I'm trying to play with some RF electronics, and am thinking of experimenting with QRSS operation. I'm not a licensed amateur, so I'm planning on using the LowFER band under FCC part 15 rules for unlicensed operation. Especially as I've been interested in building another LF receiver (WWVB) for some time.
So to get started, I need to find a suitable crystal and divisor network from what I have in my junkbox. This is the third time I've used this program, so I thought others might find it useful, as well.
WARNING: The code is ugly and bruteforce. I just wanted results, rather than something "nice". Normally, I wouldn't want to show something this ugly, but if it helps other people play with RF, it's worth it.
To use it, you need a file (nominally "crystals.txt") that holds a list of the oscillators, crystals and/or resonators you have in your junkbox. My current list is:
# Crystals, Oscillators & ceramic resonators. All freq in MHz # TODO: find way to specify tolerance: currently just using stated fre +quency # and can count number of zeros... # TODO: Modify to allow M or K to be decimal point to specify other fr +eq ranges # Oscillators (cans) OSC 20.0000 OSC 28.3220 OSC 42.0000 OSC 36.0000 OSC 36.00000 OSC 50.000 OSC 60.0 # Bare crystals XTAL .032768 XTAL .0384 XTAL 1.8432 XTAL 3.58 (tiny) XTAL 4.00 (tiny) XTAL 4.5 XTAL 7.3728 XTAL 6.000 XTAL 6.5536 XTAL 8.0000 XTAL 14.31818 XTAL 20.000 XTAL 27.115 XTAL 27.125 XTAL 30.000 XTAL 31 # UNKNOWNS: # BOMAF C6 1W14T  MEW 31 PU479312 # A set marked 301502 on side, top: (14,4,3,2,1,5,8,9,11,12,1 +3) # Ceramic resonators RES 4.000 RES 8.000 RES 7.37 # ? "737 Cm 219" RES 10.7 RES 16.93
Then you run the program specifying the frequency you want, and the tolerance you'll accept. It'll rip through the list and determine how to get the frequency you want. Then it reports the possibilities. Each line is formatted like:
actualFreq (error) = startFreq / divisor "div =" factors
For example, if I'm looking to build an oscillator for 175kHz, and am willing to accept a 500 Hz error, I'd run it like so:
$ ./find_xtal.pl 175k k5 175,000 ( 0) = 42,000,000 / 240 div = 2^4 * 3 * 5 = 27,125,000 / 155 div = 5 * 31 174,935 ( 65) = 27,115,000 / 155 div = 5 * 31 174,927 ( 73) = 60,000,000 / 343 div = 7^3 175,141 ( 141) = 31,000,000 / 177 div = 3 * 59 174,827 ( 173) = 28,322,000 / 162 div = 2 * 3^4 174,825 ( 175) = 50,000,000 / 286 div = 2 * 11 * 13 174,757 ( 243) = 36,000,000 / 206 div = 2 * 103 174,611 ( 389) = 14,318,180 / 82 div = 2 * 41 175,409 ( 409) = 10,700,000 / 61 div = 61 175,438 ( 438) = 20,000,000 / 114 div = 2 * 3 * 19 = 30,000,000 / 171 div = 3^2 * 19 = 60,000,000 / 342 div = 2 * 3^2 * 19 = 50,000,000 / 285 div = 3 * 5 * 19 174,536 ( 464) = 16,930,000 / 97 div = 97 175,476 ( 476) = 7,370,000 / 42 div = 2 * 3 * 7
What luck! I can hit the exact frequency I was thinking about using the 42 MHz crystal oscillator and three TTL chips from my junk box: (7490:divide by 10, 7492:divide by 12, 7476:divide by 2). That'll put me right in the middle of the LowFER band.
(Note: I used k5 instead of 500 for tolerance, because 500 doesn't work, and I didn't want to spend the time to fix it. I'll try to update this post if I fix it or make any further improvements.)
Anyway, I hope someone out there may find it useful. The code follows...
#!/usr/bin/perl my $usage = <<EOHDR; find_xtal.pl <DesiredFreq> [<Tolerance>] Find the crystal (and integral divisor(s)) to come within <Toleran +ce> Hz of <DesiredFreq>. Specify frequency as #.# or #m# for MHz, or #k# fo +r kHz. If you don't specify <Tolerance>, 5% is assumed. EOHDR # # 20130216 MCMason: Added tolerance, sort by error (asc) and funkiness + within error. # Find *all* divs within TOL range # 20120429 MCMason: original version # # TODO: Trim report: If there are multiple ways to get same freque +ncy, keep only # the top N of best one(s) (based on funkiness) # TODO: Improve funkiness calculation: e.g. try to get something p +roportional # to # dividers & difficulty. Perhaps something like $e * ( +log($f)/log(2)) # use strict; use warnings; use autodie; my $dbg_funkiness=0; my $Freq = shift or die $usage; $Freq = txt_2_MHz($Freq); my $Tol = shift; if (defined $Tol) { $Tol = txt_2_MHz($Tol); } else { $Tol = $Freq * .05; } my @crystals = read_crystals(); my @results; sub compute { my ($Fx, $d) = @_; return undef unless $d; my $f = int($Fx / $d); my $err = abs($Freq  $f); return undef if $err > $Tol; my $ar = [ factorize($d) ]; my $funk = factor_funkiness(@$ar); return [ $Fx, $d, $f, $err, $ar, $funk ]; } for my $Fx (@crystals) { # 'perfect' divisor my $div = $Fx / $Freq; # Surrounding integral divisors my $d = int $div; while (my $ar = compute($Fx, $d)) { last if ! defined $ar; my ($xFx, $xd, $xf, $xerr, $xar, $xfunk) = @$ar; print "Fx:$xFx, d:$xd, f:$xf, err:$xerr, funk:$xfunk (div:$di +v)\n" if $dbg_funkiness; push @results, $ar; $d; } $d = int $div+1; while (my $ar = compute($Fx, $d)) { last if ! defined $ar; my ($xFx, $xd, $xf, $xerr, $xar, $xfunk) = @$ar; print "Fx:$xFx, d:$xd, f:$xf, err:$xerr, funk:$xfunk (div:$di +v)\n" if $dbg_funkiness; push @results, $ar; ++$d; } } print "\n\n"; @results = sort { $$a[3] <=> $$b[3] or $$a[5] <=> $$b[5] } @results; my $prev_freq = 1; for my $ar (@results) { my $funk = $dbg_funkiness ? " \tfunkiness=$$ar[5]" : ""; if ($prev_freq ne $$ar[2]) { printf "%11.11s (%7s) = %11s / %4s div = %16s$funk\n", commify($$ar[2]), commify($$ar[3]), commify($$ar[0]), +commify($$ar[1]), join(" * ", @{$$ar[4]}); $prev_freq = $$ar[2]; } else { printf " = %11s / %4s div = %16s$funk\n +", commify($$ar[0]), commify($$ar[1]), join(" * ", factorize($$ar[1])); } } sub txt_2_MHz { my $t = shift; my $orig = $t; # Multiplier (default is MHz for /\d+\.\d*/) my $mult = 1000000; if ($t =~ /m/i) { # 1M8432 => 1,843,000 $t =~ s/m/./; $mult = 1000000; } elsif ($t =~ /k/i) { # 32k768 => 32,768 $t =~ s/k/./; $mult = 1000; } if ($t =~ /\d+(\.\d*)?\.\d+/) { return $t * $mult; } die "txt_2_MHz: Unexpected input '$orig' => '$t', can't determine +frequency."; } sub read_crystals { open my $FH, '<', 'crystals.txt'; my %freqs; while (<$FH>) { next if /^\s*#/; next if /^\s*$/; if (/([.09]+)/) { $freqs{$1*1_000_000}=0; } } return sort { $a <=> $b } keys %freqs; } sub commify { my $s = shift; $s =~ s/(\d)(\d\d\d)$/$1,$2/; $s =~ s/(\d)(\d\d\d,)/$1,$2/g; return $s; } sub factor_funkiness { my @factors = @_; my $funkiness = 0; print "funkiness(",join(", ", @factors),")\n" if $dbg_funkiness; for my $t (@factors) { my ($f, $e) = split /\^/, $t; $e=1 if ! defined $e; if ($f == 2) { $funkiness += .05*$e; # no change } elsif ($f == 3) { $funkiness += 0.5 * $e; } elsif ($f == 5) { $funkiness += 0.6 * $e; } elsif ($f < 32) { $funkiness += 1 * $e; } else { $funkiness += 2 * $e; } print "\t$t => $funkiness\n" if $dbg_funkiness; } print "\tfinal == $funkiness\n" if $dbg_funkiness; return $funkiness; } sub factorize { my $num = shift; my @factors = (); my $factor=2; my $exp=0; while ($num%$factor == 0) { ++$exp; $num /= $factor; } if ($exp > 1) { push @factors, "$factor^$exp" } elsif ($exp) { push @factors, $factor } $factor=3; while ($factor*$factor <= $num) { $exp = 0; while ($num%$factor == 0) { ++$exp; $num /= $factor; } if ($exp > 1) { push @factors, "$factor^$exp" } elsif ($exp) { push @factors, $factor } $factor += 2; } push @factors, $num if $num>1; return @factors; }
Now I just need to do a little more research to find exactly what the best areas in the LowFER band are for QRSS to see if my intended 175kHz transmitter/receiver would be in a good spot.
...roboticus
When your only tool is a hammer, all problems look like your thumb.


Replies are listed 'Best First'.  

Re: Get a specific frequency from various crystals & divisors
by nettmaus (Novice) on Feb 18, 2013 at 05:54 UTC  
Re: Get a specific frequency from various crystals & divisors
by blue_cowdawg (Monsignor) on Feb 18, 2013 at 15:57 UTC  
Re: Get a specific frequency from various crystals & divisors
by GotToBTru (Prior) on Mar 01, 2013 at 23:09 UTC  
by roboticus (Chancellor) on Mar 02, 2013 at 06:28 UTC  
by NateTut (Deacon) on Nov 06, 2013 at 20:34 UTC 