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
7878static 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
112121struct tuning_state tunes [MAX_TUNES ];
113122int tune_count = 0 ;
114123
115- int boxcar = 1 ;
116124int comp_fir_size = 0 ;
117125int 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
531596void 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