Skip to content

Commit 3223086

Browse files
committed
rtl_power: fixed size bins, refactoring
1 parent e98ab40 commit 3223086

File tree

1 file changed

+156
-90
lines changed

1 file changed

+156
-90
lines changed

src/rtl_power.c

Lines changed: 156 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
#define AUTO_GAIN -100
7373
#define BUFFER_DUMP (1<<12)
7474

75-
#define MAXIMUM_RATE 2800000
75+
#define MAXIMUM_RATE 2400000
7676
#define MINIMUM_RATE 1000000
7777

7878
static volatile int do_exit = 0;
@@ -107,12 +107,20 @@ struct tuning_state
107107
//pthread_mutex_t buf_mutex;
108108
};
109109

110+
struct channel_solve
111+
/* details required to find optimal tuning */
112+
{
113+
int upper, lower, bin_spec;
114+
int bw_wanted, bw_needed;
115+
int bin_e, downsample, downsample_passes;
116+
double crop, crop_tmp;
117+
};
118+
110119
/* 3000 is enough for 3GHz b/w worst case */
111-
#define MAX_TUNES 3000
120+
#define MAX_TUNES 4000
112121
struct tuning_state tunes[MAX_TUNES];
113122
int tune_count = 0;
114123

115-
int boxcar = 1;
116124
int comp_fir_size = 0;
117125
int peak_hold = 0;
118126

@@ -122,10 +130,9 @@ void usage(void)
122130
"rtl_power, a simple FFT logger for RTL2832 based DVB-T receivers\n\n"
123131
"Use:\trtl_power -f freq_range [-options] [filename]\n"
124132
"\t-f lower:upper:bin_size [Hz]\n"
125-
"\t (bin size is a maximum, smaller more convenient bins\n"
126-
"\t will be used. valid range 1Hz - 2.8MHz)\n"
133+
"\t valid range for bin_size is 1Hz - 2.8MHz\n"
127134
"\t[-i integration_interval (default: 10 seconds)]\n"
128-
"\t (buggy if a full sweep takes longer than the interval)\n"
135+
"\t buggy if a full sweep takes longer than the interval\n"
129136
"\t[-1 enables single-shot mode (default: off)]\n"
130137
"\t[-e exit_timer (default: off/0)]\n"
131138
//"\t[-s avg/iir smoothing (default: avg)]\n"
@@ -134,19 +141,20 @@ void usage(void)
134141
"\t[-g tuner_gain (default: automatic)]\n"
135142
"\t[-p ppm_error (default: 0)]\n"
136143
"\tfilename (a '-' dumps samples to stdout)\n"
137-
"\t (omitting the filename also uses stdout)\n"
144+
"\t omitting the filename also uses stdout\n"
138145
"\n"
139146
"Experimental options:\n"
140147
"\t[-w window (default: rectangle)]\n"
141-
"\t (hamming, blackman, blackman-harris, hann-poisson, bartlett, youssef)\n"
148+
"\t hamming, blackman, blackman-harris, hann-poisson, bartlett, youssef\n"
142149
// kaiser
143-
"\t[-c crop_percent (default: 0%%, recommended: 20%%-50%%)]\n"
144-
"\t (discards data at the edges, 100%% discards everything)\n"
145-
"\t (has no effect for bins larger than 1MHz)\n"
150+
"\t[-c crop_percent (default: 0%% suggested: 20%%)]\n"
151+
"\t discards data at the edges, 100%% discards everything\n"
152+
"\t has no effect for bins larger than 1MHz\n"
153+
"\t this value is a minimum crop size, more may be discarded\n"
146154
"\t[-F fir_size (default: disabled)]\n"
147-
"\t (enables low-leakage downsample filter,\n"
148-
"\t fir_size can be 0 or 9. 0 has bad roll off,\n"
149-
"\t try with '-c 50%%')\n"
155+
"\t enables low-leakage downsample filter,\n"
156+
"\t fir_size can be 0 or 9. 0 has bad roll off,\n"
157+
"\t try with '-c 50%%'\n"
150158
"\t[-P enables peak hold (default: off)]\n"
151159
"\t[-D direct_sampling_mode, 0 (default/off), 1 (I), 2 (Q), 3 (no-mod)]\n"
152160
"\t[-O enable offset tuning (default: off)]\n"
@@ -155,16 +163,17 @@ void usage(void)
155163
"\tdate, time, Hz low, Hz high, Hz step, samples, dbm, dbm, ...\n\n"
156164
"Examples:\n"
157165
"\trtl_power -f 88M:108M:125k fm_stations.csv\n"
158-
"\t (creates 160 bins across the FM band,\n"
159-
"\t individual stations should be visible)\n"
166+
"\t creates 160 bins across the FM band,\n"
167+
"\t individual stations should be visible\n"
160168
"\trtl_power -f 100M:1G:1M -i 5m -1 survey.csv\n"
161-
"\t (a five minute low res scan of nearly everything)\n"
169+
"\t a five minute low res scan of nearly everything\n"
162170
"\trtl_power -f ... -i 15m -1 log.csv\n"
163-
"\t (integrate for 15 minutes and exit afterwards)\n"
171+
"\t integrate for 15 minutes and exit afterwards\n"
164172
"\trtl_power -f ... -e 1h | gzip > log.csv.gz\n"
165-
"\t (collect data for one hour and compress it on the fly)\n\n"
173+
"\t collect data for one hour and compress it on the fly\n\n"
166174
"Convert CSV to a waterfall graphic with:\n"
167-
"\t http://kmkeen.com/tmp/heatmap.py.txt \n");
175+
" https://github.com/keenerd/rtl-sdr-misc/blob/master/heatmap/heatmap.py \n"
176+
"More examples at http://kmkeen.com/rtl-power/\n");
168177
exit(1);
169178
}
170179

@@ -424,89 +433,145 @@ void rms_power(struct tuning_state *ts)
424433
ts->samples += 1;
425434
}
426435

427-
void frequency_range(char *arg, double crop)
428-
/* flesh out the tunes[] for scanning */
429-
// do we want the fewest ranges (easy) or the fewest bins (harder)?
436+
/* todo, add errors to parse_freq, solve_foo */
437+
438+
int parse_frequency(char *arg, struct channel_solve *c)
430439
{
431440
char *start, *stop, *step;
432-
int i, j, upper, lower, max_size, bw_seen, bw_used, bin_e, buf_len;
433-
int downsample, downsample_passes;
434-
double bin_size;
435-
struct tuning_state *ts;
436441
/* hacky string parsing */
437442
start = arg;
438443
stop = strchr(start, ':') + 1;
439444
stop[-1] = '\0';
440445
step = strchr(stop, ':') + 1;
441446
step[-1] = '\0';
442-
lower = (int)atofs(start);
443-
upper = (int)atofs(stop);
444-
max_size = (int)atofs(step);
447+
c->lower = (int)atofs(start);
448+
c->upper = (int)atofs(stop);
449+
c->bin_spec = (int)atofs(step);
445450
stop[-1] = ':';
446451
step[-1] = ':';
447-
downsample = 1;
448-
downsample_passes = 0;
452+
return 0;
453+
}
454+
455+
int solve_giant_bins(struct channel_solve *c)
456+
{
457+
c->bw_wanted = c->bin_spec;
458+
c->bw_needed = c->bin_spec;
459+
tune_count = (c->upper - c->lower) / c->bin_spec;
460+
c->bin_e = 0;
461+
c->crop_tmp = 0;
462+
return 0;
463+
}
464+
465+
int solve_downsample(struct channel_solve *c, int boxcar)
466+
{
467+
int scan_size, bins_wanted, bins_needed, ds_next;
468+
double bw;
469+
470+
scan_size = c->upper - c->lower;
471+
tune_count = 1;
472+
c->bw_wanted = scan_size;
473+
474+
bins_wanted = (int)ceil((double)scan_size / (double)c->bin_spec);
475+
c->bin_e = (int)ceil(log2(bins_wanted));
476+
while (1) {
477+
bins_needed = 1 << c->bin_e;
478+
c->crop_tmp = (double)(bins_needed - bins_wanted) / (double)bins_needed;
479+
if (c->crop_tmp >= c->crop) {
480+
break;}
481+
c->bin_e++;
482+
}
483+
484+
while (1) {
485+
bw = (double)scan_size / (1.0 - c->crop_tmp);
486+
c->bw_needed = (int)bw * c->downsample;
487+
488+
if (boxcar) {
489+
ds_next = c->downsample + 1;
490+
} else {
491+
ds_next = c->downsample * 2;
492+
}
493+
if (((int)bw * ds_next) > MAXIMUM_RATE) {
494+
break;}
495+
496+
c->downsample = ds_next;
497+
if (!boxcar) {
498+
c->downsample_passes++;}
499+
}
500+
501+
return 0;
502+
}
503+
504+
int solve_hopping(struct channel_solve *c)
505+
{
506+
int i, scan_size, bins_all, bins_crop, bins_2;
507+
scan_size = c->upper - c->lower;
449508
/* evenly sized ranges, as close to MAXIMUM_RATE as possible */
450-
// todo, replace loop with algebra
451-
for (i=1; i<1500; i++) {
452-
bw_seen = (upper - lower) / i;
453-
bw_used = (int)((double)(bw_seen) / (1.0 - crop));
454-
if (bw_used > MAXIMUM_RATE) {
509+
for (i=1; i<MAX_TUNES; i++) {
510+
c->bw_wanted = scan_size / i;
511+
bins_all = scan_size / c->bin_spec;
512+
bins_crop = (int)ceil((double)bins_all / (double)i);
513+
c->bin_e = (int)ceil(log2(bins_crop));
514+
bins_2 = 1 << c->bin_e;
515+
c->bw_needed = bins_2 * c->bin_spec;
516+
c->crop_tmp = (double)(bins_2 - bins_crop) / (double)bins_2;
517+
if (c->bw_needed > MAXIMUM_RATE) {
518+
continue;}
519+
if (c->crop_tmp < c->crop) {
455520
continue;}
456521
tune_count = i;
457522
break;
458523
}
459-
/* unless small bandwidth */
460-
if (bw_used < MINIMUM_RATE) {
461-
tune_count = 1;
462-
downsample = MAXIMUM_RATE / bw_used;
463-
bw_used = bw_used * downsample;
464-
}
465-
if (!boxcar && downsample > 1) {
466-
downsample_passes = (int)log2(downsample);
467-
downsample = 1 << downsample_passes;
468-
bw_used = (int)((double)(bw_seen * downsample) / (1.0 - crop));
469-
}
470-
/* number of bins is power-of-two, bin size is under limit */
471-
// todo, replace loop with log2
472-
for (i=1; i<=21; i++) {
473-
bin_e = i;
474-
bin_size = (double)bw_used / (double)((1<<i) * downsample);
475-
if (bin_size <= (double)max_size) {
476-
break;}
477-
}
478-
/* unless giant bins */
479-
if (max_size >= MINIMUM_RATE) {
480-
bw_seen = max_size;
481-
bw_used = max_size;
482-
tune_count = (upper - lower) / bw_seen;
483-
bin_e = 0;
484-
crop = 0;
524+
return 0;
525+
}
526+
527+
void frequency_range(char *arg, double crop, int boxcar)
528+
/* flesh out the tunes[] for scanning */
529+
{
530+
struct channel_solve c;
531+
struct tuning_state *ts;
532+
int i, j, buf_len;
533+
534+
parse_frequency(arg, &c);
535+
c.downsample = 1;
536+
c.downsample_passes = 0;
537+
c.crop = crop;
538+
539+
if (c.bin_spec >= MINIMUM_RATE) {
540+
fprintf(stderr, "Mode: rms power\n");
541+
solve_giant_bins(&c);
542+
} else if ((c.upper - c.lower) < MINIMUM_RATE) {
543+
fprintf(stderr, "Mode: downsampling\n");
544+
solve_downsample(&c, boxcar);
545+
} else {
546+
fprintf(stderr, "Mode: normal\n");
547+
solve_hopping(&c);
485548
}
549+
c.crop = c.crop_tmp;
550+
486551
if (tune_count > MAX_TUNES) {
487552
fprintf(stderr, "Error: bandwidth too wide.\n");
488553
exit(1);
489554
}
490-
buf_len = 2 * (1<<bin_e) * downsample;
555+
buf_len = 2 * (1<<c.bin_e) * c.downsample;
491556
if (buf_len < DEFAULT_BUF_LENGTH) {
492557
buf_len = DEFAULT_BUF_LENGTH;
493558
}
494559
/* build the array */
495560
for (i=0; i<tune_count; i++) {
496561
ts = &tunes[i];
497-
ts->freq = lower + i*bw_seen + bw_seen/2;
498-
ts->rate = bw_used;
499-
ts->bin_e = bin_e;
562+
ts->freq = c.lower + i*c.bw_wanted + c.bw_wanted/2;
563+
ts->rate = c.bw_needed;
564+
ts->bin_e = c.bin_e;
500565
ts->samples = 0;
501-
ts->crop = crop;
502-
ts->downsample = downsample;
503-
ts->downsample_passes = downsample_passes;
504-
ts->avg = (long*)malloc((1<<bin_e) * sizeof(long));
566+
ts->crop = c.crop;
567+
ts->downsample = c.downsample;
568+
ts->downsample_passes = c.downsample_passes;
569+
ts->avg = (long*)malloc((1<<c.bin_e) * sizeof(long));
505570
if (!ts->avg) {
506571
fprintf(stderr, "Error: malloc.\n");
507572
exit(1);
508573
}
509-
for (j=0; j<(1<<bin_e); j++) {
574+
for (j=0; j<(1<<c.bin_e); j++) {
510575
ts->avg[j] = 0L;
511576
}
512577
ts->buf8 = (uint8_t*)malloc(buf_len * sizeof(uint8_t));
@@ -518,14 +583,14 @@ void frequency_range(char *arg, double crop)
518583
}
519584
/* report */
520585
fprintf(stderr, "Number of frequency hops: %i\n", tune_count);
521-
fprintf(stderr, "Dongle bandwidth: %iHz\n", bw_used);
522-
fprintf(stderr, "Downsampling by: %ix\n", downsample);
523-
fprintf(stderr, "Cropping by: %0.2f%%\n", crop*100);
524-
fprintf(stderr, "Total FFT bins: %i\n", tune_count * (1<<bin_e));
586+
fprintf(stderr, "Dongle bandwidth: %iHz\n", c.bw_needed);
587+
fprintf(stderr, "Downsampling by: %ix\n", c.downsample);
588+
fprintf(stderr, "Cropping by: %0.2f%%\n", c.crop*100);
589+
fprintf(stderr, "Total FFT bins: %i\n", tune_count * (1<<c.bin_e));
525590
fprintf(stderr, "Logged FFT bins: %i\n", \
526-
(int)((double)(tune_count * (1<<bin_e)) * (1.0-crop)));
527-
fprintf(stderr, "FFT bin size: %0.2fHz\n", bin_size);
528-
fprintf(stderr, "Buffer size: %i bytes (%0.2fms)\n", buf_len, 1000 * 0.5 * (float)buf_len / (float)bw_used);
591+
(int)((double)(tune_count * (1<<c.bin_e)) * (1.0-c.crop)));
592+
fprintf(stderr, "FFT bin size: %iHz\n", c.bin_spec);
593+
fprintf(stderr, "Buffer size: %i bytes (%0.2fms)\n", buf_len, 1000 * 0.5 * (float)buf_len / (float)c.bw_needed);
529594
}
530595

531596
void retune(rtlsdr_dev_t *d, int freq)
@@ -657,7 +722,16 @@ void scanner(void)
657722
}
658723
ds = ts->downsample;
659724
ds_p = ts->downsample_passes;
660-
if (boxcar && ds > 1) {
725+
if (ds_p) { /* recursive */
726+
for (j=0; j < ds_p; j++) {
727+
downsample_iq(fft_buf, buf_len >> j);
728+
}
729+
/* droop compensation */
730+
if (comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) {
731+
generic_fir(fft_buf, buf_len >> j, cic_9_tables[ds_p]);
732+
generic_fir(fft_buf+1, (buf_len >> j)-1, cic_9_tables[ds_p]);
733+
}
734+
} else if (ds > 1) { /* boxcar */
661735
j=2, j2=0;
662736
while (j < buf_len) {
663737
fft_buf[j2] += fft_buf[j];
@@ -668,15 +742,6 @@ void scanner(void)
668742
if (j % (ds*2) == 0) {
669743
j2 += 2;}
670744
}
671-
} else if (ds_p) { /* recursive */
672-
for (j=0; j < ds_p; j++) {
673-
downsample_iq(fft_buf, buf_len >> j);
674-
}
675-
/* droop compensation */
676-
if (comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) {
677-
generic_fir(fft_buf, buf_len >> j, cic_9_tables[ds_p]);
678-
generic_fir(fft_buf+1, (buf_len >> j)-1, cic_9_tables[ds_p]);
679-
}
680745
}
681746
remove_dc(fft_buf, buf_len / ds);
682747
remove_dc(fft_buf+1, (buf_len / ds) - 1);
@@ -769,6 +834,7 @@ int main(int argc, char **argv)
769834
int interval = 10;
770835
int fft_threads = 1;
771836
int smoothing = 0;
837+
int boxcar = 1;
772838
int single = 0;
773839
int direct_sampling = 0;
774840
int offset_tuning = 0;
@@ -868,7 +934,7 @@ int main(int argc, char **argv)
868934
exit(1);
869935
}
870936

871-
frequency_range(freq_optarg, crop);
937+
frequency_range(freq_optarg, crop, boxcar);
872938

873939
if (tune_count == 0) {
874940
usage();}

0 commit comments

Comments
 (0)