Fast Algorithm for Rational Approximation of Floating Point Numbers

When doing frequency synthesis with fractional-N PLLs, one often needs to find a rational approximation of a floating point number with the constraint that the numerator must not be larger than a certain number. The more exact the approximation is, the closer the actual frequency will be to the desired one.

The integer part is obviously easy, but the fractional part requires a more sophisticated algorithm. One such algorithm is based on Farey sequences and the formula for finding the next fraction between two Farey neighbors.

The Farey sequence of order N consists of all completely reduced fractions between 0 and 1. So e.g. the Farey sequence of order 3 consists of {0/1, 1/3, 1/2, 2/3, 1/1}. A sequence of a higher order contains all the terms of all lower orders and then some more. So, with a fractional-N PLL where the denominator is limited to some value D, the possible fractional parts of the N in the PLL is precisely the fractions present in the Farey sequency of order D. And our goal is to find the best one to approximate the fractional part of the desired N, i.e. a number between 0 and 1.

There is a neat and useful formula for finding the next fraction that will appear between two Farey neighbors as the order of the sequence is increased. If a/b < c/d are neighbors in some Farey sequence, the next term to appear between them is the mediant (a+c)/(b+d). So an algorithm to find better and better rational approximations to a number x is to

  1. Start with the Farey sequence of order 1, i.e. {0/1, 1/1}, where a/b = 0/1 and c/d = 1/1 are neighbors.
  2. Calculate the mediant (a+c)/(b+d) = (0+1)/(1+1) = 1/2.
  3. Figure out which of a/b and c/d are further from x than the mediant and replace it with the mediant.
  4. Go to 2 if the denominator is not yet larger than what is allowed.
  5. Use the best result previous to the denominator becoming too large.

This algorithm is described on this archived web page.

This seems fine, until one considers some special cases. Say e.g. the target number is 0.000 001 and the maximum allowed denominator is 2 000 000. The algorithm would narrow down the top end of the interval from 1/1 to 1/2 to 1/3 to 1/4… in each successive iteration. It would thus take a million steps before the perfect approximation of 1/1 000 000 is reached. This is hardly efficient in this case, even though it converges quickly in many (most) other cases.

A way to speed up these degenerate cases is to not just take a single step in each iteration, but instead figure out how many times in a row either a/b or c/d will be discarded in favor of the mediant. It turns out that this is not too hard to do.

If e.g. a/b is to be discarded K times in a row, the resulting number will be (a + K*c)/(b + K*d). So how many times K will a/b be discarded until it is time to narrow down the interval from the other end? Set (a + K*c)/(b + K*d) = x and solve for K, which gives K = (x*b – a)/(c – x*d). K is often not an integer, so then select the biggest integer smaller than K.

If it is instead c/d that is to be discarded, the formula for K is (c – x*d)/(x*b – a).

With this improvement, it takes just one step to get to 1/1 000 000 instead of a million. Quite an improvement.

I wrote some C-code to implement this improved algorithm. Here it is:

// Farey sequence-based rational approximation of numbers.
// Per Magnusson, 2024
// MIT licence, http://www.opensource.org/licenses/mit-license.php

#include <cstdint>
#include <cmath>

typedef struct {
  uint32_t numerator;
  uint32_t denominator;
} rational_t;


// Find the best rational approximation to a number between 0 and 1.
//
// target - a number between 0 and 1 (inclusive)
// maxdenom - the maximum allowed denominator
//
// The algorithm is based on Farey sequences/fractions. See
// https://web.archive.org/web/20181119092100/https://nrich.maths.org/6596
// a, b, c, d notation from
// https://en.wikipedia.org/wiki/Farey_sequence is used here (not
// from the above reference). I.e. narrow the interval between a/b
// and c/d by splitting it using the mediant (a+c)/(b+d) until we are 
// close enough with either endpoint, or we have a denominator that is
// bigger than what is allowed.
// Start with the interval 0 to 1 (i.e. 0/1 to 1/1).
// A simple implementation of just calculating the mediant (a+c)/(b+d) and
// iterating with the mediant replacing the worst value of a/b and c/d is very
// inefficient in cases where the target is close to a rational number
// with a small denominator, like e.g. when approximating 10^-6.
// The straightforward algorithm would need about 10^6 iterations as it 
// would try all of 1/1, 1/2, 1/3, 1/4, 1/5 etc. To resolve this slow
// convergence, at each step, it is calculated how many times the 
// interval will need to be narrowed from the same side and all those 
// steps are taken at once.
rational_t rational_approximation(double target, uint32_t maxdenom)
{
  rational_t retval;
  double mediant;  // float does not have enough resolution 
                      // to deal with single-digit differences 
                      // between numbers above 10^8.
  double N, Ndenom, Ndenom_min; 
  uint32_t a = 0, b = 1, c = 1, d = 1, ac, bd, Nint;
  const int maxIter = 1000;

  if(target > 1) {
    // Invalid
    retval.numerator = 1;
    retval.denominator = 1;
    return retval;
  }
  if(target < 0) {
    // Invalid
    retval.numerator = 0;
    retval.denominator = 1;
    return retval;
  }
  if(maxdenom < 1) {
    maxdenom = 1;
  }

  mediant = 0;
  Ndenom_min = 1/((double) 10*maxdenom);
  int ii = 0;
  // Farey approximation loop
  while(1) {
    ac = a+c;
    bd = b+d;
    if(bd > maxdenom || ii > maxIter) {
      // The denominator has become too big, or too many iterations.  
    	// Select the best of a/b and c/d.
      if(target - a/(double)b < c/(double)d - target) {
        ac = a;
        bd = b;
      } else {
        ac = c;
        bd = d;
      }
      break;
    }
    mediant = ac/(double)bd;
    if(target < mediant) {
      // Discard c/d as the mediant is closer to the target.
      // How many times in a row should we do that?
      // N = (c - target*d)/(target*b - a), but need to check for division by zero
      Ndenom = target * (double)b - (double)a;
      if(Ndenom < Ndenom_min) {
        // Division by zero, or close to it!
        // This means that a/b is a very good approximation
        // as we would need to update the c/d side a 
        // very large number of times to get closer.
        // Use a/b and exit the loop.
        ac = a;
        bd = b;
        break;
      }
      N = (c - target * (double)d)/Ndenom;
      Nint = floor(N);
      // Check if the denominator will become too large
      if(d + Nint*b > maxdenom) {
        // Limit N, as the denominator would otherwise become too large
        N = (maxdenom - d)/(double)b;
        Nint = floor(N);
      }
      // Fast forward to a good c/d.
      c = c + Nint*a;
      d = d + Nint*b;

    } else {

      // Discard a/b as the mediant is closer to the target.
      // How many times in a row should we do that?
      // N = (target*b - a)/(c - target*d), but need to check for division by zero
      Ndenom = (double)c - target * (double)d;
      if(Ndenom < Ndenom_min) {
        // Division by zero, or close to it!
        // This means that c/d is a very good approximation 
        // as we would need to update the a/b side a 
        // very large number of times to get closer.
        // Use c/d and exit the loop.
        ac = c;
        bd = d;
        break;
      }
      N = (target * (double)b - a)/Ndenom;
      Nint = floor(N);
      // Check if the denominator will become too large
      if(b + Nint*d > maxdenom) {
        // Limit N, as the denominator would otherwise become too large
        N = (maxdenom - b)/(double)d;
        Nint = floor(N);
      }
      // Fast forward to a good a/b.
      a = a + Nint*c;
      b = b + Nint*d;
    }
    ii++;
  }

  retval.numerator = ac;
  retval.denominator = bd;
  return retval;
}

To test the algorithm on an Arduino (actually a Pi Pico), I wrote the following code:

#include <arduino.h>

typedef struct {
  double target;
  uint32_t maxdenom;
  uint32_t expected_numerator;
  uint32_t expected_denominator;
} rational_test_case_t;

void test_rational_approx()
{
  rational_t result;

  rational_test_case_t test[] = { 
    {0, 3000, 0, 1},
    {1, 3000, 1, 1},
    {0.5, 3000, 1, 2},
    {0.5+1/3001.0, 3000, 751, 1501},
    {1/3001.0, 2500, 1, 2500},
    {1/3001.0, 1500, 0, 1},
    {1/3001.0, 3001, 1, 3001},
    {0.472757439, 1816, 564, 1193},
    {0.472757439, 1817, 859, 1817},
  };
  uint32_t n_tests = sizeof(test)/sizeof(test[0]);

  for(uint32_t ii = 0; ii < n_tests; ii++) {
    result = rational_approximation(test[ii].target, test[ii].maxdenom);
    Serial.print("target = ");
    Serial.print(test[ii].target, 7);
    Serial.print(", maxdenom = ");
    Serial.print(test[ii].maxdenom);
    Serial.print(", approx = ");
    Serial.print(result.numerator);
    Serial.print("/");
    Serial.print(result.denominator);
    if(result.numerator == test[ii].expected_numerator && result.denominator == test[ii].expected_denominator) {
      Serial.println(" OK");
    } else {
      Serial.print(" Expected: ");
      Serial.print(test[ii].expected_numerator);
      Serial.print("/");
      Serial.println(test[ii].expected_denominator);
    }
  }
}

The printout from the code is:

target = 0.0000000, maxdenom = 3000, approx = 0/1 OK
target = 1.0000000, maxdenom = 3000, approx = 1/1 OK
target = 0.5000000, maxdenom = 3000, approx = 1/2 OK
target = 0.5003332, maxdenom = 3000, approx = 751/1501 OK
target = 0.0003332, maxdenom = 2500, approx = 1/2500 OK
target = 0.0003332, maxdenom = 1500, approx = 0/1 OK
target = 0.0003332, maxdenom = 3001, approx = 1/3001 OK
target = 0.4727574, maxdenom = 1816, approx = 564/1193 OK
target = 0.4727574, maxdenom = 1817, approx = 859/1817 OK

I also implemented the algorithm in an Excel sheet:

The excel document is available here:

Farey approximation Excel sheet

Reverse-Engineering the OK2BWN Compact 3,5F ARDF Receiver

I maintain a set of simple ARDF (amateur radio direction finding, or “fox hunting”) receivers of the type Compact 3,5F from OK2BWN for use by beginners. A problem with some of the receivers is that the gain cannot be set low enough, which means that the audio becomes unbearably loud close to a transmitter. I could not find any schematics online, so I decided to reverse-engineer the receiver so that I could figure out what to change to allow lower gain.

The OK2BWN Compact 3,5F Receiver
The inside of the receiver

The receiver is built on a single-layer board using some through-hole components on the top side and surface mount components on the bottom. The discrete SMD components are large (1206 size), so the resistors have their values printed on them. This all makes the tracing of the circuitry relatively simple. I photographed both sides, flipped the photo of the top side and overlayed them. Then it was “just” a matter of sketching out the circuitry, a process that involved a couple of false starts before it all converged to something that looks like a direct conversion receiver.

Even before starting, I had a good idea of the general architecture as the receiver contained a single SA612 mixer with an analog VCO and an LM386 audio amplifier, but no crystal or ceramic IF filters (so it was not a superheterodyne). Since there was only one mixer it also could not be an image reject receiver, hence receiving both the desired and undesired sideband, which is also evident when tuning the receiver as a CW signal appears for two different tuning settings close to each other. Another thing that was obvious from using the receiver and looking at the parts inside, was that the signal from the E-field antenna was combined with the signal from the H-field antenna by a few turns on the ferrite core when the “antenna selector” switch was pushed to power the E-field amplifier located on a separate board.

Below are the photos of the top and the bottom of the board, as well as a combined photo where the flipped top side components are shown somewhat transparently over the bottom side photo.

The bottom side of the PCB
The top side mirrored to help with reverse engineering
The top side transparently overlayed on the bottom side.

Below is the schematics I came up with. As far as I know, it is correct, but I give no guarantees.

Reverse-engineered schematics

An annotated photo of the bottom of the board is shown below.

Annotated layout

I have not bothered to desolder and measure unmarked components like capacitors, so apart from the electrolytics, I do not know their values. I wrote 100 nF on capacitors I think only serve the function of DC-blocks, supply decoupling or low-pass filtering of the varactor bias, but this is just a guess. They could have some other value, although that is unlikely to be critical. The capacitors in series with the varactors might theoretically have some low value to adjust/reduce the tuning range, so there the 100 nF guess could be more wrong.

There are a few mystery components in the circuit. The four-pin through-hole part (Q1A, Q1B in the schematic) near the antenna is unmarked, except for a white stripe. Based on its place in the circuit and the components around it, I have guessed it is a double JFET in cascode configuration. While there are indeed dual JFETs available today, I could not find one with this package. My guess is that it is an obsolete part. Also, the varactors are unmarked, but the conclusion they are varactors is more solid than the guess regarding the dual JFET.

A JFET cascode amplifier is a good choice as a low-noise-amplifier for a ferrite antenna as it has high input impedance and low noise. Here it has a tuned drain circuit which increases the gain at the cost of some added complexity (and risk of oscillation?). The Q is limited by the 1.5 kohm input impedance of the SA612.

I suppose having a tuned RF amplifier was necessary to get enough gain for weak signals. The other gain elements are the mixer (nominally 14 dB) and the LM386. The RF amplifier is the only adjustable gain element in the receiver, which is perhaps an unusual choice. The input gate is DC-biased to 0 V via the ferrite antenna while the source is connected to a decoupled (C35) resistive network around the gain potentiometer, which allows changing the operating point of the JFET to select a suitably steep part of the ID-VGS curve to get the desired gain.

Adjusting the minimum gain

Now that we have the schematics, what do we need to do to allow more attenuation/less gain? The obvious place to look is near the existing gain control circuit. When potentiometer R4 is at its maximum counter-clockwise (CCW) position, the gate-source of JFET Q1A is maximally back biased to operate at the shallowest ID-VGS point and thus lowest gain. But we need it to be lower still.

VGS(off), i.e. the (negative) voltage from gate to source of a JFET required to turn the transistor fully off is not a well-controlled parameter. It can easily vary by a factor larger than two (sometimes larger than five) between devices with the same part number. As discussed the way gain is adjusted in this receiver is largely by adjusting VGS, so it is no surprise that different receivers will have different minimum gains.

To reduce the minimum gain for a receiver, it is fortunately quite easy to make a modification that results in a more negative VGS when the gain pot is maximally turned CCW. Just reduce R1 that together with the gain pot R4 (and R3 and R6) forms a voltage divider from +5V. A lower R1 value means the source will be biased to a higher voltage when the gain pot is at max CCW, but it will have very little effect when the pot is at the opposite end.

I found that parallelling the 33 kΩ R1 with 100 kΩ to get 24.8 kΩ resulted in a decent minimum gain for most receivers I modified. One receiver that was not quite as bad received 130 kΩ across R1 (26.3 kΩ total).

Below is a photo of a board with a blue 100 kΩ resistor soldered on top of the 33 kΩ R1 that was there originally.

R1 has received 100 kΩ on top.

Further comments and observations

The antenna is wound using two parallel and mirrored windings. This is supposed to eliminate any possibility of direction error. A direction-finding ferrite antenna should have a null exactly when the magnetic field-lines enter perpendicular to the core, but if there is a single winding on the core with some distance between the start and end of winding, there will be one effective turn over a part of the broadside ferrite core, leading to a slight offset in the null. How big a problem this is in practice is a bit unclear, but it generally is considered good practice to use two windings in parallel, wound symmetrically in opposite directions to cancel this parasitic broadside turn.

There are three varactor-tuned circuits in the receiver that need to be simultaneously tuned to the same frequency as set by the tuning knob: The antenna, the low-noise amplifier and the local oscillator. Getting all three circuits to agree on the frequency for all settings of the tuning dial might have been a challenge when designing the receiver. There is a trimmer capacitor across the antenna that can be adjusted to ensure the antenna agrees with the LO for at least one frequency and the two inductors are also trimmable to allow alignment on at least one (mid-band) frequency. And maybe it is not a big problem if the stages are slightly out of tune near the band edges.

The shielding of the receiver is a bit relaxed. There is a copper clad board acting as shields on each side of the circuit board and ferrite antenna. The shield at the front panel is only connected to the electronics via a thin wire and the shield in the bottom of the box does not seem to have any secure connection at all to the rest of the circuitry. This is not good shielding practice, but perhaps it is good enough at 3.5 MHz. The PCB seems to have provisions along the edges for better shield connections, but maybe this turned out to be unnecessary.

There are shield walls above and below the ferrite antenna, solidly soldered to the copper clad board. Having a good electric shield around the ferrite antenna is crucial for getting distinct nulls in the antenna pattern.

The way the front-back ambiguity of the figure-eight pattern of the ferrite is resolved in this kind of receiver is a bit shaky. When pressing the “antenna selector” button, the E-field antenna signal is injected into the ferrite core and picked up by the ordinary receiver chain. If it is in phase with the signal from the H-field antenna, the total signal gets louder, but if there is close to a 180 degree phase difference, the signal gets weaker, at least if the signals have close to the same amplitude. So by comparing the signal strength with one broadside of the ferrite towards the transmitter with the signal strength with the receiver rotated 180 degrees (which gives a 180 degree phase shift/inversion of the H-field signal, but does not affect the E-field signal), one can figure out if the transmitter is ahead or behind. The big problem however is that the signal strength from the E-field antenna is highly dependent on e.g. the height above ground, so it may not have close to the ideal amplitude and thus not be close to cancelling out the H-field signal when they have opposing phase. And particularly in close vicinity of the transmitter (in the near-field), the two parts of the field may have an unexpected phase and amplitude relationship. This may make it hard to distinguish which orientation of the receiver produces the strongest signal. Trying this at different heights, from knee level to above head level, to get different E-field strengths is often necessary. It would be better if the phase could be compared more directly, but this would add significantly to the complexity of the receiver, which is not an option for a low-cost receiver like this.

The LC-oscillator built with the (now obsolete) SA612 mixer is a configuration I have not seen before. It is not one of the topologies suggested in the data sheet.

A somewhat dubious component choice is the 10-V rated electrolytic C26 connected across the 9-V battery. This is a little too little voltage margin for my taste. A cap with a rating of at least 16 V should be used in that position in my opinion.

There is a through-hole 78L05 5-V regulator on the board, powering parts of the circuitry that benefits from a stable (not dependent on battery discharge level) voltage and also circuits that are not 9-V-tolerant.

Modifiering av 2-m-saxen ROX-2X

Den här artikeln publicerades först i QTC nr 5 2023.

Via SM0GNS Peder fick jag tag på en ganska komplett byggsats för kretskortet till en ROX-2X radiopejlmottagare [1] för 2-m-bandet. Antagligen hade Peder tröttnat på att jag lånat färdigbyggda mottagare av honom och tyckte det var dags att jag skaffade en egen, vilket jag inte var sen att nappa på.

Efter att jag lött ihop mottagaren och verifierat att den fungerade så började jag fundera på om den skulle gå att förbättra på något sätt. Främst var jag intresserad av att få till en digitalt styrd lokaloscillator för att eliminera risken att man har fel frekvens inställd, till exempel till följd av att man oavsiktligt kommer åt frekvensratten under en tävling, men jag hittade även ett par andra saker som kunde åtgärdas och några extra finesser att lägga till. Dessutom hittade jag på en egen antennkonstruktion som verkar fungera bra.

Den här artikeln beskriver hur mottagaren fungerar och vad jag gjorde för förändringar i den. Jag har inte haft tillgång till någon bygg-, komponent- eller funktionsbeskrivning utöver vad som finns på [1], så det jag skriver är mestadels baserat på schemat och egna tester.

Figur 1 visar schemat för ROX-2X med rutor som markerar vilka delar jag kommer att ändra.

Figur 1. Kopplingsschemat för ROX-2X.

Jag har ritat om schemat för att göra det aningen mer lättläst och för att enkelt kunna rita in ändringar (och för att jag inte bett om tillåtelse att återpublicera originalschemat). En liten detalj som jag fixat redan här är att den gröna lysdioden D5 som visar att spänningen är påslagen var felvänd i originalschemat.

RF-delarna sköts av en SA636 som är en krets främst tänkt för FM-mottagning i exempelvis DECT-telefoner. Tyvärr har den slutat tillverkas och är nog svår att få tag på, så inget att basera framtida konstruktioner på, men ett antal exemplar av ROX-2X lär finnas därute och många av idéerna här är mer allmänt tillämpliga, så förhoppningsvis är den här artikeln ändå av intresse.

Radiopejling på 2-m-bandet sker med sändare som skickar ut morsekod genom att AM-modulera en bärvåg. Så hur kan en krets för FM-mottagning fungera i detta sammanhang? Tricket är att man inte alls använder de vanliga utsignalerna, utan bara lyssnar på RSSI- utgången (Received Signal Strength Indicator). SA636 har nämligen en snabb RSSI som duger som AM-demodulator i denna tillämpning. En annan kreativ lösning i konstruktionen är att förstärkningen delvis ställs in genom att tappa av bias-ström på RF-ingångarna. Att detta fungerar kan man möjligen ana sig till av databladet som visar ett förenklat schema av ingångssteget, men möjligheten verkar inte alls nämnas där. Kanske är det något konstruktören experimenterat sig fram till.

Ingångsimpedansen är ganska hög, 700 Ω parallellt med 3.5 pF single-ended, dvs 1400 Ω parallellt med 1.75 pF differentiellt, så anpassningen mot Yagi-antennens impedans i trakterna av 40 ohm mår bra av en ganska rejäl upptransformering av impedansen med en faktor 9 (1:3 varvtalsförhållande) via transformatorn L2. Med trimkondensatorn C3 kan man avstämma anpassningen. Mer om det senare.

Mellanfrekvensen är på klassiska 10,7 MHz och två keramiska filter för denna frekvens vaskar fram den önskade signalen. Antennen och dess matchningskomponenter får duga som preselektorfilter före mixern för att undertrycka det oönskade sidbandet.

Lokaloscillatorn är LC-baserad och avstäms med en kapacitansdiod (D2) som biaseras av en potentiometer (RV1.2). Spolen (L3.1) har en kärna man måste trimma för att hamna i rätt frekvensområde.

RSSI-signalen innehåller dels den önskade AM-demodulerade audiosignalen (om än logaritmiskt demodulerad, men låg distorsion är inte en prioritet här) och dels en DC-nivå som indikerar signalstyrka även den tid då modulering saknas. Sändare kan antingen slå av bärvågen mellan morsepipen eller låta bärvågen ligga kvar omodulerad. I det senare fallet finns det en radiosignal att pejla mot hela tiden under ett sändningspass, alltså även när den AM-demodulerade signalen inte innehåller något hörbart, och det drar den här mottagaren nytta av som vi ska se.

RSSI-signalen förgrenas dels till en förstärkare, U4, som driver hörlurarna med den demodulerade signalen, dels via ett lågpassfilter, R8-C16, till en spänningsstyrd oscillator, U2, och dels till transistorn Q1 som ska tända lysdioden LED1 vid stark signal.

U4 har en volymkontrollingång, ben 4, som påverkas av samma volymkontroll, RV2.2, som justerar biasen på RF-ingångarna enligt tidigare beskrivning. Jag är osäker på hur mycket av känsligheten som bestäms av den justerbara biaseringen av RF-ingången respektive av U4. U4 har enligt databladet ett förstärkningsområde mellan ungefär -70 och +20 dB, men toleransen på vilken förstärkning man får för en viss styrspänning är hög, så olika exemplar av kretsen kan ha rejält olika förstärkning vid en och samma spänning. Men det är inte något större problem i den här tillämpningen.

Jag upplevde att den maximala ljudstyrkan var onödigt hög, så för att minska den kopplade jag om lite så att styrspänningen tas från en punkt som ligger ett diodfall lägre än normalt. Det krävde en kapad kopparbana vid ben 4 på U4 samt en kort bygel från C22 till ben 3 på D4. En alternativ och enklare lösning hade varit att öka värdet på R20 som ligger i serie med hörlurarna.

Ytterligare en åtgärd för att minska maximal ljudstyrka, och samtidigt göra en större del av volymrattens område användbart, var att sätta 22 k parallellt med volympotten RV2.2.

En viktig finess i mottagaren är den RSSI-styrda VCO:n U2 som används som en ”audio S-meter”. När RSSI-signalen blir tillräckligt hög börjar U2 oscillera, först med ett lågfrekvent tickande och sedan med allt högre frekvens i takt med att radiosignalen blir starkare (eller biaseringen av RF-ingången ökas). Även denna VCO-signal är kopplad till hörlurarna och den gör att det blir lättare att med hörseln avgöra små skillnader i signalstyrka eftersom de representeras med skillnader i frekvens istället för med skillnader i amplitud. Det här är väldigt användbart eftersom antenndiagrammet är ganska trubbigt, så det blir viktigt att särskilja små variationer i signalstyrka för att avgöra i vilken riktning som signalen är starkast när man sveper med antennen under pejlingen. Och som nämnts tidigare så hörs denna ton även mellan morsepipen när den vanliga audiosignalen är tyst (såvida det inte är en sändare som stänger av bärvågen mellan pipen).

Nivån på den RSSI-signal som styr U2 bestäms dels av radiosignalens styrka och dels av vilken förstärkning man ställt in på RF-ingången, men inte av den ytterligare förstärkningen i U4. Det är bara under ca halva området för volymratten som RF-förstärkningen påverkas, så när man vridit upp volymen till mer än ca hälften av max så blir inte RSSI-signalen som styr U2 högre. Vid svaga radiosignaler räcker nivån inte till att få igång U2, oavsett volyminställning, så när man är långt från sändaren får man förlita sig på den vanliga ljudstyrkan när man pejlar.

Den tredje grenen av RSSI-signalen går som sagt till Q1 och LED1. Tanken är att lysdioden ska tändas när RSSI-signalen blir stark nog, men detta fungerade inte alls. För att LED1 ska börja tändas måste nämligen RSSI bli högre än Vbe för Q1 (0,6 V) plus spänningsfallet över LED1 (ca 2 V), men den nivån uppnåddes aldrig. Antagligen fungerade det i en tidigare version av konstruktionen när R6 (som bestämmer förstärkningen hos RSSI-förstärkaren i U1) var 33k istället för 22k, men alltså inte med R6 = 22k. Med lite kreativ omkoppling så att LED1 hamnar i kollektorkretsen för Q1 istället för vid emittern, samt tillägget av ett spänningsdelande motstånd på basen så återställdes funktionen. De schemamässiga ändringarna framgår av figur 2 medan de fysiska ändringarna är utpekade i figur 3 med numrerade pilar som pekar ut följande detaljer:

  1. R100, 150 kohm från högra änden av R9 (basen på Q1) till jord.
  2. Kollektorn, ben 3, på Q1 svävar i luften. Till kollektorn är en sladd som leder till katoden på LED1 kopplad. Tunn emaljerad koppartråd användes för att inte riskera att bryta sönder Q1 när sladden rör sig.
  3. En tråd som kopplar emittern på Q1 till jord.
  4. R10 (330 ohm) har flyttats från sin ordinarie plats till mellan 5V och anoden på LED1.
  5. Sladden till anoden på LED1.

Figur 2. Modifierat kopplingsschema för ROX-2X.

Figur 3. Modifieringar för att få LED1 att fungera som avsett.

Det är tänkt att man inte ska behöva någon strömbrytare till mottagaren. Detta åstadkoms genom att gaten på PMOS-transistorn Q2 dras upp till 9 V av R18 när ingen hörlur är inkopplad. Q2 slår då av och resten av konstruktionen blir utan matning. När en hörlur är inkopplad dras gaten låg och Q2 börjar leda. Regulatorn U3 skapar en stabil 5-V-matning och lysdioden D5 indikerar att strömmen är på och att batterispänningen är högre än dryga 7 V (5 V plus spänningsfallet på ca 2 V över D5 plus lite spänning till strömbegränsaren R14). Genom att D5 sitter parallellt med spänningsregulatorn får man dels en sorts lågt-batteri-varning och dels slipper man slösa extra ström till denna diod eftersom all ström som flyter genom den också går vidare och gör nytta i resten av elektroniken. Hade dioden inte suttit där hade dess energiförbrukning istället omvandlats till värme i U3.

Tyvärr ville inte den hörlursstyrda strömbrytarfunktionen fungera. Efter lite felsökande upptäckte jag att C25, som är ritad som en polariserad kondensator i schemat, inte alls får vara polariserad. Jag hade satt dit en tantal, men i fallet att ingen hörlur är inkopplad kommer R18+R19+D6+R20 att lägga på en spänning med fel polaritet på denna, med läckström som resultat. Läckströmmen gör att gaten på Q2 inte blir hög nog för att Q2 ska slås av, så strömbrytningsfunktionen fungerade inte.

Lösningen är enkel, nämligen att ersätta C25 med en opolariserad keramisk kondensator. Om man tittar på foton på kretskortet på [1] så ser man att där sitter en keram och inte en tantal, så det är bara schemat som är lite vilseledande. Jag blev i alla fall fintad och hann konstruera in en fysisk strömbrytare som fortfarande finns kvar i mitt bygge.

Den stora förändringen jag ville göra var dock som sagt att införa en digitalt styrd lokaloscillator. Vidare ville jag hålla detta projekt någorlunda enkelt (betydligt mycket mindre ambitiöst än 80-m-mottagaren jag beskrev i en tidigare artikel [2]). Specifikt ville jag undvika att behöva konstruera (och vänta på tillverkningen av) nya mönsterkort och helst också klara mig med att använda komponenter som jag redan hade hemma. Dessa mål uppnåddes mestadels, men tidsmässigt drog det ändå ut på tiden och blev (som vanligt?) mer omfattande än jag hade tänkt mig. Inte minst mjukvaran tog tid att utveckla och har man väl en processor så är det lätt hänt att man kommer på att man kan lägga till den ena finessen efter den andra utan att behöva så värst mycket mer hårdvara.

Som digitalt styrd LO valde jag samma flexibla PLL-krets som jag använde i [2], nämligen Si5351A. Den styrs via ett I2C-interface och kan skapa tre olika fyrkantsvågor med valbar frekvens mellan ca 2,3 kHz och 200 MHz utgående från frekvensen hos en kristall. Fyrkantsvågen måste såklart filtreras för att bli en godtagbar sinus, vilket är vad som behövs för LO-ingången på SA636. Si5351A går att köpa monterad på ett litet kort tillsammans med en spänningsregulator [3] vilket gör det lätt att använda kretsen utan att göra ett eget mönsterkort.

För att skicka kommandon till Si5351A valde jag denna gång en för mig ny bekantskap, nämligen det prisvärda (runt femtilappen) och tillgängliga mikrokontrollerkortet Raspberry Pi Pico. Det innehåller en dubbelkärning ARM Cortex M0+ processor som officiellt går att klocka i upp till 133 MHz, vilket är långt mycket mer prestanda än vad som behövs här. Om man installerar rätt tillägg i senaste versionen av Arduino-miljön [4] går det bra att programmera kortet från denna för mig hemtama miljö. För att spara ström valde jag att ställa ned klockfrekvensen till 50 MHz (lägsta frekvens som är lätt inställbar i Arduinomiljön), men betydligt lägre klockfrekvenser hade också med råge gett tillräckliga prestanda.

För att det ska vara möjligt att se vad man ställt in mottagaren på för frekvens ville jag också ha någon sorts display. Jag grävde i skrotlådan och hittade en gammal 2×16 tecken alfanumerisk LCD med den vanliga HD44780 som drivkrets. Det finns färdigt stöd [5] för att prata med sådana displayer, så man behöver inte utveckla några egna lågnivårutiner.

För att styra det hela använde jag en digital ratt (encoder) och fyra membranknappar, ungefär som i [2]. Mjukvaran jag skrivit går att ladda ner från GtiHub [6]. Ett schema för kopplingarna utanför det ursprungliga kretskortet finns i figur 4.

Figur 4. Schema för kopplingarna utanför ROX-2X-kortet.

Jag byggde det hela i huvudsak på ett kopparlaminat som fick fungera både som jordplan och mekanisk support. De flesta svarta ledningarna i figur 4 utgörs därför av enbart en kort ledning ner till jordplanet. För att bygga lågpassfiltret använde jag ett litet mönsterkort ämnat för sådana filterbyggen som jag hade liggande sedan tidigare projekt.

Ett foto på hur det hela ser ut i praktiken finns i figur 5. Många av ledningarna är dragna med tunna emaljerade koppartrådar. De tar liten plats, väger inte mycket, är lätta att avisolera (genom att sticka in dem i en droppe lödtenn på spetsen av en lödpenna) och lätta att få att ligga ungefär där man vill. En nackdel är att man inte får någon färgkodning, så det kan vara svårare att reda ut vilken tråd som har vilken funktion. Ledningarna till ROX-2X-kortet drog jag dock med vanliga plastisolerade kopplingstrådar. Ofta partvinnade med en referenssignal (vanligtvis jord) för att inte få en massa onödiga jordslingor som möjligen skulle kunna skicka ut eller plocka upp störningar. LO-signalen drog jag i en tunn flexibel koax (ännu inte inkopplad i figur 5).

Figur 5. Display, filter, klockgenerator, processorkort mm, monterade på ett kopparlaminat i en 3D-utskriven låda.

Modifieringen av huvudkortet för att kunna koppla in den nya lokaloscillatorn beskrivs i figur 6. De olika pilarna anger följande:

  1. Anslutningspunkt för koaxialkabelns skärm.
  2. 2 st 100-ohmsmotstånd parallellt för att terminera koaxen i 50 ohm. Mittledaren på koaxen löds in på heta sidan av detta motstånd (ungefär där pil 3 pekar).
  3. Kapad ledning mot L3.
  4. C4 är bortplockad.
  5. C7 är bortplockad.
  6. C5 är bortplockad.

Figur 6. Modifieringar för att kunna koppla in den nya lokaloscillatorn.

I kopplingsschemorna i figurerna 2 och 4 framgår några finesser utöver vad som nämnts tidigare. Såväl RSSI-signalen som spänningen från 9-V-batteriet är indragna på analoga ingångar på processorkortet så att man kan bevaka dem från mjukvaran och presentera informationen i displayen. Batterispänningen skalas ned med en spänningsdelare för att passa området som ingångarna klarar och skyddsdioder mot 3.3 V ser till att ytterligare skydda ingångarna. En schottkydiod behövs också för att mata processorkortet från 5-V-spänningen från ROX-2X-kortet så att den matningen inte blir ihopkopplad med 5 V från USB-kabeln i de fall då man har USB inkopplad för programmering eller utläsning av information.

En aningen onödig funktion är att de två annars oanvända utgångarna från Si5351-kortet är kopplade (via dämpning i det här schemat och ännu mer dämpning i det modifierade ROX-2X-schemat i figur 2) till RF-ingången. Syftet är att kunna injicera en RF-signal med valfri frekvens så att mottagaren själv kan mäta upp karakteristiken på IF-filtret genom att svepa frekvensen på denna testsignal. Utgångarna konfigureras under testet för att drivas i motfas så att en differentiell signal skapas. Idén till den här lösningen har jag lånat från QDX-transceivern från QRP-labs [7]. Mätvärdena från RSSI-signalen, omräknade till någon sorts dB med hjälp av information i SA636-databladet, skickas ut via USB-porten till en PC där vidare analys kan göras i exempelvis Excel. Vid mätningen är det lämpligt att inte ha antennen inkopplad. Figur 7 visar inkopplingen av testsignalerna på LO-kortet medan figur 8 visar hur det ser ut där de kommer in på huvudkortet. Testsignalerna skickas i partvinnade emaljerade koppartrådar, vilket är tunnare och enklare att jobba med än koaxialkabel och fullt tillräckligt i det här fallet.

Figur 7. LO-kortet, filterkortet och inkopplingen av testsignalerna vid CLK1 och CLK2.

Figur 8. Injicering av testsignalerna på undersidan av ROX-2X-kortet. De stora seriekopplade kondensatorerna på 220 pF vardera utgör C2 som inte fick plats på ovansidan eftersom jag ville sätta transformatorn där.

Figur 9 och 10 visar resultatet från svep av såväl det önskade passbandet som spegelbandet när mottagaren var inställd på 144.75 MHz. Nivån på dämpningen utanför passbanden är inte att lita på eftersom dynamiken och brusnivån i denna test är begränsande. Inte litar jag heller fullt ut på att 1 dB skillnad på Y-axeln verkligen motsvarar 1.0 dB i verkligheten, men man får ändå en god fingervisning om hur IF-filtret beter sig.

Figur 9. Responsen i det önskade passbandet. Y-axeln är graderad i någon sorts approximativ dB-enhet.

Figur 10. Responsen i spegelbandet. Y-axeln är graderad i någon sorts approximativ dB-enhet.

Notera att den lilla asymmetrin i filtret är spegelvänd mellan signalbandet och spegelbandet, precis som väntat. Bandbredden är runt 50 kHz, vilket är lite onödigt mycket för en AM-signal. En lägre bandbredd skulle ge mindre brus, men också större risk att man måste ställa om frekvensen för att få in sändare som inte ligger så exakt i frekvens. Kristallfilter skulle kunna ge lägre bandbredd, men skulle behöva lite anpassningar eftersom de normalt har betydligt högre impedans än de 330 ohm som SA636 är gjord för.

En funktion som inte kräver någon speciell hårdvara, utan bara mjukvara, är att svepa upp 2-m-bandet och leta efter störningar för att se om mottagaren stör sig själv. Det är ju alltid en risk att de digitala delarna skapar någon ton på en olycklig frekvens som dränker signalen från en svag sändare. Figur 11 visar resultatet av två sådana svep utan inkopplad antenn. Dels ett svep som gjordes i en någorlunda störningsfri miljö (ute i trädgården några meter från huset) och dels inomhus nära en del datorutrustning. Man kan se att brusmattan från datorutrustningen överröstar eventuella interna störningar, samt att det inte finns några kraftiga interna störningar på någon viss frekvens, vilket är positivt.

Figur 11. Uppmätt brus utan antenn. Den blå kurvan är från en relativt tyst omgivning (ute i trädgården) medan den orangea är uppmätt inomhus nära diverse datorutrustning. Y-axeln är i godtyckliga dB.

Lådan har inte någon skärmning, utöver att mycket av elektroniken och sladdarna ligger nära jordplan eller är partvinnade med jordade ledningar, så kanske skulle man kunna vinna något på mer skärmning. Å andra sidan är det antennen som är klart bäst på att plocka upp störningar och jag har inte hört någon sänkning av störnivån när jag håller inne processorns resetknapp. När man gör det fortsätter Si5351 att generera LO-signalen och de analoga delarna fortsätter fungera, men processorn upphör att köra kod och prata med displayen. Så det är en rimlig test av hur mycket självstörning som förekommer, även om man egentligen skulle behöva upprepa testet vid många frekvenser över hela bandet för att vara helt säker.

Elektroniken byggdes in i en 3D-utskriven låda med display och processorkort närmast framsidan och RF-kortet på nivån bakom. I en mellannivå på sidan om klämde jag in ett batterifack byggt av kopparlaminat. Eftersom jag var tvungen att rita upp lådan i ett CAD-program inför 3D-utskriften lade jag in även övriga detaljer där och fick på så vis bra ritningsunderlag även för hur kopparlaminaten skulle sågas till. Figur 12 visar hur det hela ser ut innan botten skruvas på. Notera att en SMA-kontakt (högst upp i bilden) används för att koppla in antennen.

Figur 12. ROX-2X-kortet sitter en nivå ovanför/bakom processor och display.

Framsidan av mottagaren visas i figur 13. Övre raden i displayen visar dels batterinivån och dels RSSI-nivån via en stapel. Genom att dynamiskt skapa specialtecken i displayen kan man få fram de tecken man för tillfället behöver för att visa en viss batterinivå eller fylla ett visst antal pixlar i den högsta rutan i stapeldiagrammet. På undre raden visas aktuell frekvens samt numret på vilken av ett antal förprogrammerade frekvenser som för tillfället är vald. Om man inte tryckt på någon knapp eller vridit på frekvensratten på 10 sekunder så slutar processorn bry sig om ratten och de flesta knapparna och man kan inte ändra något. För att låsa upp måste man trycka på ”1”. I upplåst läge kan man justera frekvens med ratten, stega mellan vilken av siffrorna i frekvensinställningen man ska justera med ”1” respektive ”2”, skifta till nästa förprogrammerade frekvens med ”3” eller gå in i ett menysystem med lite fler funktioner genom att hålla inne ”4” i minst en sekund. I menysystemet kan man bland annat förprogrammera upp till 9 olika frekvenser och starta frekvenssvepen som diskuterats ovan. Under en normal tävling behöver man förhoppningsvis varken justera frekvensen eller alls titta på displayen, så det är bara volymkontrollen och hörlurarna som kommer till användning. Möjligen kan man vilja byta till beaconfrekvensen genom att trycka på ”3” när man tagit sista räven.

Figur 13. Lådan med mottagaren.

Ett par nackdelar med att bygga ut ROX-2X på det här sättet är att vikten blir lite högre (totalt knappt 600 g inklusive antenn), samt att strömförbrukningen blir mycket högre. Jag har mätt upp strax under 50 mA och med tanke på att 9-V-batterier verkar ha en kapacitet på ca 300 mAh vid denna last så kan man med marginal köra en tävling och kanske till och med två eller möjligen tre innan det är dags att byta. Att byta till ett laddningsbart LiPo-batteri med mycket högre kapacitet och kanske lägre vikt borde inte vara alltför svårt eftersom alla kretsar förutom nuvarande displayen borde kunna fungera på 3 V. Förutom displaybyte skulle det kräva att man lägger till en laddarkrets samt möblerar om runt en del spänningsregulatorer och vid volymkontrollen. Att ha två LiPo-batterier i serie för att få högre spänning skulle kräva mindre anpassning av elektroniken, men att laddningen blir lite mer komplicerad.

En mottagare är inte mycket att ha såvida man inte har en passande antenn. Om man letar runt lite på nätet kan man hitta flera måttritningar på olika varianter av pejlantenner för 2-m-bandet. Den vanligaste är nog en enkel Yagi med en direktor och en reflektor utöver den ”drivna” dipolen. En annan populär variant är HB9CV som har fördelen att bara ha två element och vara betydligt kortare, men nackdelarna att vara lite mer komplicerad med sina gamma-matcher och inte simuleringsbar med något program jag har tillgång till. Jag valde Yagi-spåret och efter att ha hittat flera olika förslag på mått så upptäckte jag programmet ”YagiCAD” där man kan simulera och optimera olika Yagi-konstruktioner. Efter en del optimerande från olika ursprungsdesigner fastnade jag för måtten i figur 14.

Figur 14. Måttritning för antennen. Spröten består av 25 mm brett stålmåttband; bommen av 16 mm elrör.

Enligt YagiCAD ska den ha strålningsdiagrammen i figur 15 vid 144.75 MHz och ett fram/bak-förhållande på över 40 dB.

Figur 15. Simulerade strålningsdiagram för antennen.

Spröten är tillverkade av 25 mm brett stålmåttband (köpt på Biltema) och som bom använde jag ett 16 mm elrör. För att fästa elementen konstruerade jag passande klämmor och skrev ut i PETG på en 3D-skrivare. Lådan för mottagaren fästs också med klämmor på bommen. För att få ett bra grepp när man håller antennen högt gjorde jag även ett handtag som fästs på bommen. Se figur 16 och 18.

Figur 16. Komplett pejlmottagare.

Antennen är ganska otymplig när man ska transportera mottagaren, så efter lite funderande konstruerade jag 3D-utskrivbara ”papiljotter” som man kan rulla upp spröten på. För att det ska fungera spetsas ändarna på spröten något och ett hål görs nära spetsen. De vassa kanterna oskadliggörs genom att strimlor av sporttejp tejpas över dem. Spetsen sticks in i mitten av papiljotten där det finns en M2.5-skruv som kan haka fast i hålet. Sedan är det bara att rulla ihop sprötet och avsluta med att sätta över en klämma som håller det hela på plats. Handtaget går också att vika åt sidan för att bli mindre skrymmande. Se figur 17 och 18.

Figur 17. Spetsat spröt med hål samt närbild på papiljotter.

Figur 18. Mottagaren hopfälld och klar för transport.

Modeller för de olika 3D-utskrivna delarna till antennen går att ladda ned från [8].

Enligt YagiCAD ska impedansen på antennen vara 33 – j5 Ω vid 145 MHz. Jag mätte upp den med en nätverksanalysator (VNWA) och fick den till 35 + j0 Ω (såklart lite beroende på antennens omgivning; i vissa positioner blev det nästan exakt 50 Ω). Eftersom jag mätte i änden på ca 6 cm koax (ca 0,2 ns) som vrider impedansen en aning medsols i Smith-diagrammet så är resultatet ännu närmare det teoretiska. Nästan lite för bra för att vara sant.

Jag mätte även inimpedansen i mottagaren och med hjälp av C3 kunde jag trimma den till 38 + j13 Ω. Av en lycklig slump (?) matchar alltså den resistiva delen nästan perfekt, medan det skulle behövas lite kapacitiv reaktans i serie med mottagarens ingång. -j13 Ω motsvaras vid 145 MHz av 84 pF. Jag hade 47 pF i lager och satte två sådana parallellt i serie med SMA-kontakten på ingången och hamnade på 32 – j0 Ω vid nästa mätning. Efter dessa övningar borde matchningen mellan antenn och mottagare vara utmärkt. Att den differentiella signalen från antennen skickas en kort bit i en koax utan föregående balun har förhoppningsvis inte alltför stor negativ inverkan.

Ännu har mottagaren inte testats i någon tävling, men preliminära tester jag gjort ser lovande ut. Jag satte i vardagsrummet, nära ett fönster, upp en enkel dipol som drivs av en signalgenerator som moduleras med lämpliga morse-signaler. Vid -10 dBm (0.1 mW) från generatorn så hörs sändaren tydligt på 500 m avstånd med nästan fri sikt. Inom 150 m hoppar audio-S-metern igång om man har volymen tillräckligt uppskruvad. Riktverkan och fram/back-förhållande upplever jag som goda. Som vanligt på 2-m-bandet så går det att hitta platser där signalen är betydligt svagare eller kommer från ett oväntat håll, men i den ganska snälla testmiljön blir det till allra största delen rättvisande bäringar och om mottagningen är konstig räcker det oftast att flytta sig en meter eller två. Jämfört med en annan, lånad, mottagare baserad på ROX-2X så upplever jag min som något mindre brusig (trots den digitala elektroniken) och lite mer känslig. Den bättre känsligheten tror jag beror på en mer omsorgsfull anpassning mellan antenn och mottagare och möjligen också på skillnader i antennkonstruktionen. Den stora skillnaden i användarupplevelse var dock att man slipper ratta på frekvensen för att hitta signalen.

Jag satte även upp en riktig 2-m-räv (gissningsvis 0,5 – 1 W) 500 m och 500 kHz från min svaga testsändare för att testa fallet att man befinner sig nära mål-beacon och vill pejla mot någon avlägsen räv. Med både min och den lånade mottagaren var jag tvungen att ta mig drygt 100 m från räven för att kunna pejla på ett meningsfullt sätt mot den svaga signalen. Det verkar alltså som att SA636 har rätt risiga blocker-prestanda och störs ut av en stark signal, även om den ligger 500 kHz från den frekvens som är inställd. Kanske en anledning att framöver konstruera en ny rävsax som baserar sig på en annan RF-kedja?

Referenser

[1] ”2m Receiver ROX-2X”, http://open-circuit.co.uk/wp/receivers/rox-2x/

[2] ”FoxScope, Rävsax för 80 m med dubbla mottagarkedjor”, QTC nummer 10 och 11, 2022.

[3] “Adafruit Si5351A Clock Generator Breakout Board”, https://www.adafruit.com/product/2045

[4] ”Arduino-Pico”, tillägg till Arduinomiljön för att programmera Raspberry Pi Pico, https://github.com/earlephilhower/arduino-pico

[5] ”LiquidCrystal”, bibliotek till Arduino, https://www.arduino.cc/reference/en/libraries/liquidcrystal/

[6] ”ROX-2X_Si5351”, programkod https://github.com/per-magnusson/ROX-2X_Si5351

[7] ”QDX – Digital Transceiver”, https://qrp-labs.com/qdx.html

[8] ”2 m Fox Hunting Antenna”, blog post with 3D models https://axotron.se/blog/2-m-fox-hunting-antenna/