AUTOMATIC DIRECTION FINDING SYSTEMS — HF/VHF/UHF
Watson-Watt, Doppler, Phased Array, and Rotary Loop RDF Systems
DOCUMENT NUMBER: TM-ADF-001 Rev A
SYSTEM: Automatic Direction Finding (ADF) Suite
COVERAGE: 160M through 20CM (1.8 MHz – 1300 MHz)
CLASSIFICATION: UNCLASSIFIED
PREPARED BY: TM Engineering
DATE: 2026-05-02
TABLE OF CONTENTS
- Chapter 1 — System Description and Theory
- Chapter 2 — Specifications
- Chapter 3 — Controls and Indicators
- Chapter 4 — Installation
- Chapter 5 — Operating Procedures
- Chapter 6 — Calibration Procedures
- Chapter 7 — NEC Antenna Models
- Chapter 8 — Firmware Architecture
- Chapter 9 — Troubleshooting
- Chapter 10 — Preventive Maintenance
- Chapter 11 — Parts List
- Appendix A — Band-by-Band Operating Notes
- Appendix B — Watson-Watt Algorithm Derivation
- Appendix C — Doppler Equation Derivation
- Appendix D — Quick Reference Card
# CHAPTER 1 — SYSTEM DESCRIPTION AND THEORY
1.1 Overview of ADF Systems
This manual covers the ADF Suite, a family of four Automatic Direction Finding (RDF) systems designed for field portable and fixed-station use across the frequency range 1.8 MHz to 1300 MHz. Each system employs a different antenna and signal processing technique suited to a specific portion of the spectrum and a set of operating conditions. All four systems share a common hardware and software platform based on the ESP32 microcontroller, u-blox NEO-8M GPS receiver, QMC5883L digital compass, SSD1306 OLED display, WiFi web interface, and Bluetooth SPP command/data interface. An optional CYD (Cheap Yellow Display, ESP32-2432S028R) touchscreen provides a full-color navigational compass rose and system status display.
Four ADF Techniques
| Technique | Firmware Module | Best Bands | Typical Accuracy | Field Setup Complexity | Relative Hardware Cost |
|---|---|---|---|---|---|
| Watson-Watt (Adcock) | esp32_adf_hf | 160M – 10M | ±3°–8° RMS | Moderate (4-element array) | Low–Moderate |
| Doppler Ring | esp32_adf_vhf | 2M – 20CM | ±1°–3° RMS | Low (single printed disk) | Low |
| Phased Array (RSSI) | esp32_adf_hf (alt mode) | 40M – 2M | ±5°–10° RMS | Moderate | Moderate |
| Rotary Loop | esp32_rotary_loop | 160M – 15M | ±2°–5° RMS | Low (single loop on motor) | Moderate |
Common Platform Features
All four systems provide:
- ESP32-WROOM-32 controller: dual-core 240 MHz, 4 MB flash, 520 KB SRAM
- GPS position reference: u-blox NEO-8M, NMEA at 9600 baud, 1PPS output for time-stamped bearings
- Compass bearing reference: QMC5883L 3-axis magnetometer, I2C, 10 Hz, with hard-iron calibration
- SSD1306 OLED: 128×64 pixels, I2C, 4-line operational display
- WiFi AP: 2.4 GHz, SSID “RF-ADF[type]”, local web interface with SVG compass rose
- Bluetooth SPP: device name “RF-ADF[type]Finder”, bidirectional command/data
- CYD touchscreen (optional slave display): ESP32-2432S028R, TFT_eSPI, BT master mode
Selecting the Appropriate ADF Technique
The following guidelines govern technique selection:
- Below 30 MHz, propagation research, fixed site or vehicle: Deploy Watson-Watt (Adcock). Provides continuous bearing with no moving parts. Works on SSB, CW, and AM signals. Array size scales with wavelength; 40M band requires 10m baseline.
- VHF/UHF foxhunting, vehicle tracking, emergency response: Deploy Doppler Ring. Small disk fits on a 1/4” camera stud. Provides unambiguous 0–360° bearing. Works on FM signals. Array diameter 35mm to 1027mm depending on band.
- Rapid deployment, single-element scanning: Deploy Phased Array RSSI mode. Uses the same Watson-Watt hardware with reduced accuracy. Good when full Adcock baseline cannot be staked out.
- LF through HF, maximum sensitivity, covert operation: Deploy Rotary Loop. Highest sensitivity for weak signal work. Null finding is very precise. Loop is inconspicuous.
1.2 Watson-Watt (Adcock) HF RDF Theory
The Adcock Array
The Adcock array consists of four vertical monopole elements arranged at the cardinal compass points (North, South, East, West) of a square with a common center. The array is used in two differential pairs: the N-S pair and the E-W pair. The North element and South element are connected in opposition (signals subtracted), and similarly for East and West. A fifth element, the sense antenna, is a vertical monopole or short whip at the array center.
The differential connection of each pair eliminates the horizontal component of incoming waves (which would otherwise introduce elevation-angle errors) and produces a figure-8 antenna pattern oriented along the pair axis.
Signal Model
Consider an arriving plane wave from bearing θ (measured clockwise from North) with amplitude E, frequency f, and phase φ:
Signal at North element: V_N = E × cos(2πft + φ) × [1 + sin(θ) × kd/2]
Signal at South element: V_S = E × cos(2πft + φ) × [1 - sin(θ) × kd/2]
Where k = 2π/λ and d = array baseline (element spacing). For small arrays (kd << 1), the differential output of the N-S pair is:
V_NS = V_N - V_S = E × k × d × sin(θ) × cos(2πft + φ)
Similarly for the E-W pair:
V_EW = V_E - V_W = E × k × d × cos(θ) × cos(2πft + φ)
Both differential outputs are in phase with each other and with the incoming wave. The ratio of their amplitudes is independent of E, k, d, and f — it depends only on θ.
The Watson-Watt Bearing Formula
The bearing of the incoming signal is extracted by:
θ = atan2(V_NS_rms, V_EW_rms)
Where V_NS_rms and V_EW_rms are the RMS values of the respective differential outputs. The atan2 function returns a value in the range -180° to +180°, giving bearing in the mathematical convention; this is converted to compass bearing (0–360°, clockwise from North) in software.
The 180° Ambiguity
The differential outputs are proportional to sin(θ) and cos(θ) respectively. Because sin(θ) = sin(180°-θ) when combined with cos, the atan2 function alone cannot distinguish between θ and θ+180°. The system does not know if the transmitter is to the North or the South, for example.
Sense antenna ambiguity resolution: The sense antenna receives an omnidirectional signal V_SENSE proportional to E×cos(2πft+φ). When the sense signal is combined in quadrature (90° phase shift) with the loop output, the result is:
V_loop + jV_sense = cardioid pattern = 1 + sin(θ) (for N-S aligned loop)
This cardioid has a single null and a single peak. The bearing of the transmitter is in the direction of the cardioid peak. By comparing the sense-enhanced bearing to the ambiguous atan2 bearing, the firmware selects the correct 180° quadrant.
In practice, the ESP32 firmware applies the sense signal by sampling the sense channel separately and comparing its phase to the N-S output. If the N-S and sense signals are in phase, the transmitter is in the positive sin(θ) half-space (North side). If out of phase, the transmitter is in the South half-space, and 180° is added to the raw atan2 result.
Phase-Matched Feed Lines
For the Watson-Watt formula to be valid, the two outputs of each differential pair must arrive at the differential amplifier with equal electrical delays. Any phase imbalance between the North and South feed lines, for example, introduces a systematic bearing error. The error magnitude in degrees is approximately:
ΔΘ_error ≈ Δφ_degrees / (k × d × sin(θ))
Feed lines must be cut to equal electrical length (same physical length and same coax type). Phase matching to better than 5° is required for ±5° bearing accuracy. Verification is performed with a NanoVNA or network analyzer (see Section 6.1).
Balun Function
Each element pair is connected to the differential amplifier through a current-mode balun (wound on FT-50-43 toroid core, 10 turns trifilar). The balun converts the balanced (floating) differential signal from the antenna pair to an unbalanced (single-ended) 50Ω output suitable for the INA128 instrumentation amplifier input. Without the balun, common-mode interference (QRN, power line fields) appears as a false bearing.
The FT-50-43 core is used for HF (1.8–54 MHz). For operation below 3 MHz, a second winding may be added to increase low-frequency inductance. The minimum acceptable common-mode rejection of the balun is 40 dB across the operating band.
LNA Role
The differential outputs from a small Adcock array are weak. For a baseline of 3.5m (appropriate for 40M band), the signal at the INA128 input is approximately 20 dB below the signal at a single monopole. The INA128 instrumentation amplifier provides gain of 40–60 dB (set by external resistor) with common-mode rejection >80 dB. An optional GALI-84 MMIC LNA may be added before the INA128 to overcome feed line insertion loss on longer cable runs (>10m).
Accuracy Factors
The following factors limit bearing accuracy in practice:
| Factor | Typical Error Contribution | Mitigation |
|---|---|---|
| Element phase imbalance | ±2°–5° | Match feed lines, verify with VNA |
| Element gain imbalance | ±1°–3° | Balance check (equal signal levels) |
| Site multipath | ±5°–20° | Select open site, average multiple readings |
| Compass error | ±1°–3° | Hard-iron calibration, check for local disturbances |
| GPS declination | ±0.5°–1° | Use current IGRF model |
| ADC noise | ±0.5°–1° | Average 8–16 readings |
| Ground asymmetry | ±3°–8° | Site survey, avoid asymmetric ground |
Overall accuracy: A well-calibrated system on a good site typically achieves ±3°–5° RMS. On a compromised site, ±8°–15° may be the practical limit.
1.3 Doppler RDF Theory
Electronic Antenna Rotation
The Doppler direction finding principle exploits the frequency shift that occurs when a receiver is in motion relative to a transmitter. If the receiver antenna is moved in a circular arc at velocity v, the received frequency is shifted by:
Δf = (f₀ × v / c) × cos(θ - φ)
Where θ is the bearing to the transmitter, φ is the instantaneous angular position of the moving antenna, and c is the speed of light.
Rather than physically rotating an antenna, the Doppler RDF system electronically switches among four (or more) stationary elements arranged on a circle. Each element is connected to the receiver in sequence. This creates the same frequency modulation as if a single antenna were physically rotating, but with no moving parts and at a switching rate that can be precisely controlled.
1kHz FM Subcarrier Creation
The standard switching sequence activates each element in turn at a rate of 1000 Hz (for a 4-element array, each element is active for 250 µs, cycling through N, E, S, W). This produces a 1 kHz frequency modulation on the received FM signal. The phase of this 1 kHz subcarrier, relative to a 1 kHz reference signal generated by the firmware, is equal to the bearing θ of the transmitter.
The switching is implemented with a FST3253 analog multiplexer (or PE4259 RF switch for VHF/UHF). The FST3253 is rated for signals up to approximately 400 MHz with low on-resistance (5Ω typical). For 33cm and 20cm bands, a PE4259 SPDT switch is cascaded to achieve the required 1–1.3 GHz performance.
Goertzel Algorithm for Phase Extraction
The Goertzel algorithm is a computationally efficient method for computing the discrete Fourier transform at a single frequency. For extracting the 1 kHz component from the ADC sample stream, it is far more efficient than a full FFT.
The algorithm operates as follows. For a block of N samples at sample rate f_s, with target frequency bin k = N × f_target / f_s:
ω = 2π × k / N
coeff = 2 × cos(ω)
Processing loop (for each sample x[n]):
s[n] = x[n] + coeff × s[n-1] - s[n-2]
At the end of the block, the complex DFT value at frequency f_target is:
X = s[N] - s[N-1] × exp(-jω)
Real part: X_re = s[N] - s[N-1] × cos(ω)
Imaginary part: X_im = s[N-1] × sin(ω)
The phase of the 1 kHz component is:
φ_measured = atan2(X_im, X_re)
And the bearing is:
θ = φ_measured - φ_reference + offset_correction
Where φ_reference is the phase of the 1 kHz switching reference signal (known exactly from the firmware timer) and offset_correction accounts for array orientation and cable delays (determined during calibration).
IQ Phase Extraction Alternative
An alternative to Goertzel is to multiply the ADC sample stream by a 1 kHz sine and cosine reference (I/Q demodulation), then low-pass filter and integrate:
I = sum(x[n] × cos(2π × 1000 × n / f_s)) over N samples
Q = sum(x[n] × sin(2π × 1000 × n / f_s)) over N samples
φ = atan2(Q, I)
Both methods give equivalent results. The Goertzel method requires fewer multiplications (2 per sample vs. 2 per sample plus filtering), so it is used in the ESP32 implementation.
Array Diameter Optimum
The modulation depth (and thus signal-to-noise ratio of the bearing estimate) is proportional to the circumference of the switching circle. The optimum array diameter for maximum modulation depth while maintaining reasonable aperture size is:
d_opt = λ/2 ÷ π ≈ 0.159λ
In practice, a circle diameter of 0.5λ (half-wavelength) is used as a compromise between modulation depth and practical array size. For the 2M band (146 MHz), λ = 2.05m, so the array diameter is 1.027m. For the 20cm band (1296 MHz), the diameter is only 115mm.
Effect of smaller array: If the array is smaller than 0.5λ, the modulation depth decreases proportionally, reducing the SNR of the extracted phase and increasing bearing jitter. For arrays smaller than 0.2λ, the modulation depth may be too low for reliable bearing in typical field conditions.
Ambiguity
The Doppler system is unambiguous. The electronic rotation direction is fixed (N → E → S → W → N), and the phase of the resulting FM subcarrier is directly proportional to the transmitter bearing in 0–360°. There is no 180° ambiguity as in the loop or Watson-Watt system (before sense resolution). This makes the Doppler system particularly suitable for rapid bearing acquisition without an additional sense antenna.
Accuracy
At high SNR (>30 dB), the Doppler system achieves ±0.5°–1° RMS bearing accuracy. At 20 dB SNR (typical field condition), accuracy is ±1°–3° RMS. Below 10 dB SNR, bearing scatter increases rapidly. The Doppler method requires an FM signal (the transmitter must be frequency-modulating). It does not work on CW or SSB signals unless they are passed through a discriminator and the resulting audio contains identifiable tone components.
1.4 Phased Array RDF Theory
Cardioid Pattern Formation
A 2-element phased array with 90° inter-element phase shift produces a cardioid radiation/reception pattern. The array factor for two elements spaced d apart with signal from bearing θ and phase progression ψ:
AF(θ) = 1 + exp(j × (k × d × cos(θ) + ψ))
For d = λ/4 and ψ = 90°, this produces a cardioid pointing in the direction of the lagging element. By selecting which element is lagging (via a relay or PIN switch), the pattern is steered left or right. The null of the cardioid points directly opposite the peak, 180° away.
Butler Matrix for Multi-Beam Output
The Butler matrix is a passive beamforming network using hybrid couplers and fixed phase shifters. For a 4-element array, a 4×4 Butler matrix produces four simultaneous output ports, each corresponding to a beam pointing in one of four orthogonal directions (typically 45°, 135°, 225°, 315° for a 0°-referenced array). The RSSI at each port is compared to find the beam with the highest received signal level; the transmitter is within the ±45° sector of that beam.
Delay Line Calculation
The 90° hybrid coupler and delay lines in the phased array use transmission line segments. For a required 90° phase shift at frequency f:
L = (c × VF) / (4 × f)
Where: - L = physical length of the delay line (meters) - c = 3 × 10⁸ m/s (speed of light in vacuum) - VF = velocity factor of the transmission line (0.66 for RG-58, 0.82 for RG-6) - f = frequency (Hz)
Example: 90° delay at 146 MHz using RG-58 (VF = 0.66):
L = (3×10⁸ × 0.66) / (4 × 146×10⁶) = 0.338m = 33.8 cm
RSSI Comparison Method
The RSSI (Received Signal Strength Indicator) comparison method uses the AD8307 logarithmic detector to measure the received signal level at each output port of the Butler matrix or phased array. The bearing is estimated by finding the beam with the highest RSSI:
Coarse bearing = index of max(RSSI[0], RSSI[1], RSSI[2], RSSI[3]) × 90°
Gaussian Interpolation for Sub-Step Resolution
The coarse bearing is refined by fitting a Gaussian curve to the three highest RSSI values:
θ_fine = θ_peak + (step/2) × (RSSI[peak+1] - RSSI[peak-1]) /
(2×RSSI[peak] - RSSI[peak-1] - RSSI[peak+1])
This sub-step interpolation typically reduces the RMS error from ±45° to ±5°–10° for a 4-element array with 90° step size.
2-Element vs. 4-Element Comparison
| Parameter | 2-Element Cardioid | 4-Element Butler Matrix |
|---|---|---|
| Number of beam positions | 2 (left/right) | 4 (N, E, S, W) |
| Angular resolution (coarse) | 180° | 90° |
| Angular resolution (with Gaussian) | ±15°–20° | ±5°–10° |
| Number of switch states | 2 | 4 |
| Ambiguity | 180° without sense | None with 4 beams |
| Hardware complexity | Low | Moderate |
1.5 Rotary Loop RDF Theory
Loop Antenna Figure-8 Pattern
A small loop antenna has a figure-8 (bidirectional) azimuth pattern. The received voltage from a signal at bearing θ (measured from the loop axis) is:
V(θ) = V_max × cos(θ)
The power pattern is therefore:
G(θ) = cos²(θ)
This produces a sharp null when the loop plane is aligned with the direction of arrival (loop axis perpendicular to the incoming wave), and a peak when the loop plane is perpendicular to the direction of arrival (loop axis parallel to the signal direction).
In practice, bearing is determined by rotating the loop to find the null (minimum signal), which is much sharper than the peak and thus gives a more precise bearing. The null occurs at θ = 90° from the transmitter direction.
Null Bandwidth and Bearing Resolution
The null depth and sharpness determine bearing resolution. For an ideal loop with null depth D (in dB below the peak):
Null half-angle = arcsin(10^(-D/20))
For a 30 dB null (which is achievable with a well-constructed loop), the null half-angle is:
θ_null = arcsin(10^(-30/20)) = arcsin(0.0316) ≈ 1.8°
This means bearing to within ±2° is theoretically achievable. In practice, site effects, loop asymmetry, and ground proximity degrade the null to 20–25 dB, giving a practical bearing resolution of ±3°–5°.
Q Factor Effect on Null Sharpness
The loop’s Q factor affects its frequency selectivity and sensitivity but does not directly affect the null sharpness of the pattern. However, a high-Q loop (Q > 50) will have a narrow bandwidth, requiring careful tuning to keep the loop resonant on the operating frequency. The null depth is primarily determined by the physical symmetry of the loop construction and its feed system, not the Q.
A practical Q of 50–150 at HF frequencies is typical for a well-constructed air-core loop. Higher Q improves sensitivity at a given frequency but narrows the usable bandwidth.
Wheeler Formula for Small Loop Inductance
The inductance of a square single-turn loop of side length a (in meters) made from wire of radius r (in meters) is given by the Wheeler approximation:
L = (2 × μ₀ × a / π) × (ln(a/r) - 0.774)
For the 400mm square loop made from 4mm (radius = 2mm) copper tube:
a = 0.400 m
r = 0.002 m
L = (2 × 4π×10⁻⁷ × 0.400 / π) × (ln(0.400/0.002) - 0.774)
= (3.2×10⁻⁷) × (ln(200) - 0.774)
= (3.2×10⁻⁷) × (5.298 - 0.774)
= (3.2×10⁻⁷) × 4.524
= 1.45 µH
This sets the tuning capacitor requirement: for 7 MHz, the required capacitance is:
C = 1 / ((2πf)² × L)
= 1 / ((2π × 7×10⁶)² × 1.45×10⁻⁶)
= 356 pF
This is in the practical range for variable capacitors and varactor-based tuning.
Tuning Requirement
To operate the loop as a receiving antenna with maximum sensitivity, the loop must be resonated at the operating frequency by a series or parallel capacitor. Resonance increases the voltage at the amplifier input by a factor equal to the Q of the circuit.
The tuning capacitor range must cover all operating frequencies. For 160M–15M (1.8–21 MHz), the required capacitance range is approximately:
| Band | Frequency | Required C |
|---|---|---|
| 160M | 1.85 MHz | 5090 pF |
| 80M | 3.65 MHz | 1307 pF |
| 40M | 7.15 MHz | 341 pF |
| 30M | 10.12 MHz | 170 pF |
| 20M | 14.2 MHz | 86 pF |
| 17M | 18.1 MHz | 53 pF |
| 15M | 21.2 MHz | 39 pF |
This range (39–5090 pF) is too wide for a single varactor. The implementation uses a bank of switched fixed capacitors (relay-switched) to select the approximate range, with a varactor (BB112, Cmin ≈ 10 pF, Cmax ≈ 100 pF) for fine tuning. The ESP32 tunes automatically by sweeping the varactor voltage and finding peak RSSI via the AD8307 detector.
Motor Drive
The loop is rotated by a NEMA 17 stepper motor (200 steps/revolution) connected to the loop shaft through a 3:1 GT2 belt reduction. This gives:
Angular resolution = 360° / (200 × 3) = 0.6°/step
With DRV8825 microstepping at 1/16 step, the resolution is:
0.6° / 16 = 0.0375°/step (approximately 0.038°)
The firmware uses 2°/step for the coarse full-scan (360° in 180 steps) and 0.25°/step for fine scanning near the null. Total coarse scan time at 2°/step with 100ms settle time per step is approximately 3 minutes.
Sense Antenna for 180° Ambiguity Resolution
The loop pattern has two nulls (at θ = ±90° from the loop axis) and produces a bearing modulo 180°. To resolve the 180° ambiguity, a small vertical sense antenna is added. The sense antenna signal is combined with the loop signal in quadrature to produce a cardioid pattern with a single null and a single peak. The transmitter is in the direction of the cardioid peak.
The firmware implements sense resolution by activating a relay to connect the sense antenna through a 90° hybrid (or RC phase shift network) and measuring whether the combined signal increases or decreases. If it increases, the transmitter is in the forward half-space; if it decreases, 180° is added to the null bearing.
1.6 GPS Integration
Purpose
The GPS receiver provides two critical functions: absolute geographic position (latitude, longitude, altitude) for multi-point triangulation, and accurate time for time-difference-of-arrival (TDOA) applications (future capability).
Magnetic Declination Correction
All bearings in this system are reported as magnetic bearings (referenced to magnetic North as read by the QMC5883L compass). To convert to true bearings (referenced to geographic North, required for mapping and triangulation), the magnetic declination must be applied:
True bearing = Magnetic bearing + Magnetic declination
The declination is positive (East) when magnetic North is East of true North and negative (West) when magnetic North is West of true North. The firmware computes declination from GPS latitude and longitude using a simplified IGRF (International Geomagnetic Reference Field) lookup table covering the continental United States. For operation outside this area, the user can override with the DECL command.
PPS Signal
The GPS NEO-8M produces a 1 Pulse Per Second (PPS) output synchronized to UTC within ±30 ns. This PPS is connected to an ESP32 GPIO with an interrupt handler that timestamps each bearing reading. Time-stamped bearings from multiple sites, transmitted via WiFi or stored for later comparison, enable TDOA geolocation (transmitter location from time-of-arrival differences at known receiver positions).
NMEA Sentence Parsing
The firmware parses the following NMEA sentences from the GPS:
$GPGGA — Global Positioning Fix Data
Fields: UTC time, latitude, N/S, longitude, E/W, fix quality,
number of satellites, HDOP, altitude, altitude units,
geoid separation, correction age, differential reference station
$GPRMC — Recommended Minimum Navigation Information
Fields: UTC time, status (A=active), latitude, N/S, longitude, E/W,
speed over ground (knots), track angle (degrees true),
date, magnetic variation, E/W
The GGA sentence provides position for declination calculation and triangulation display. The RMC sentence provides date and speed for dead reckoning (future use).
Haversine Formula
The great-circle bearing from a known position to a target position (for waypoint display) is computed with the Haversine formula:
Given: (lat1, lon1) = observer position
(lat2, lon2) = target position (in radians)
Δlat = lat2 - lat1
Δlon = lon2 - lon1
a = sin²(Δlat/2) + cos(lat1) × cos(lat2) × sin²(Δlon/2)
c = 2 × atan2(√a, √(1-a))
d = R × c (R = 6371 km)
x = sin(Δlon) × cos(lat2)
y = cos(lat1) × sin(lat2) - sin(lat1) × cos(lat2) × cos(Δlon)
bearing = atan2(x, y) converted to degrees (0–360°)
1.7 Digital Compass Integration
QMC5883L Sensor
The QMC5883L is a 3-axis Hall-effect magnetometer on a single chip, communicating over I2C at address 0x0D. Configuration parameters:
| Register | Value | Meaning |
|---|---|---|
| MODE | 0x01 | Continuous measurement mode |
| ODR | 0x0C | 200 Hz output data rate |
| RNG | 0x00 | ±2 Gauss full-scale range |
| OSR | 0xC0 | 512 oversample ratio |
The I2C address is fixed at 0x0D. The SDA and SCL lines require 4.7kΩ pull-up resistors to 3.3V.
Hard-Iron Calibration
Hard-iron distortion is caused by permanently magnetized materials near the sensor (such as the battery, steel screws, or the DC-DC converter inductor). It appears as a fixed offset added to all three axes. The effect is to shift the center of the circular locus (X vs. Y plot during rotation) away from the origin.
Calibration procedure: Rotate the entire controller assembly slowly through two complete 360° rotations while the firmware records the minimum and maximum values of the X and Y magnetic field measurements. The hard-iron offsets are:
offset_X = (X_max + X_min) / 2
offset_Y = (Y_max + Y_min) / 2
The corrected readings are:
X_corr = X_raw - offset_X
Y_corr = Y_raw - offset_Y
Soft-Iron Compensation
Soft-iron distortion is caused by magnetically permeable materials near the sensor (such as iron chassis parts). It distorts the circular locus into an ellipse. Full soft-iron correction requires fitting an ellipsoid to the 3D data and computing a 3×3 correction matrix. This is not implemented in the current firmware (noted as a limitation). For most field uses, hard-iron correction alone is sufficient.
Tilt Compensation
When the compass is not held level, the X and Y field measurements are contaminated by the Z-axis field component. The MPU6050 accelerometer (on the same I2C bus, address 0x68) provides pitch and roll angles for tilt compensation:
X_tilt = X_corr × cos(pitch) + Z_corr × sin(pitch)
Y_tilt = Y_corr × cos(roll) + X_corr × sin(roll) × sin(pitch)
- Z_corr × cos(pitch) × sin(roll)
The corrected heading is then:
heading = atan2(-Y_tilt, X_tilt)
Magnetic Declination
The heading from the above calculation is magnetic heading. True heading is obtained by adding the magnetic declination (positive East, negative West). The firmware looks up declination from a stored table indexed by integer degrees of latitude and longitude.
Heading accuracy: After hard-iron calibration, with tilt compensation, the heading accuracy is typically ±1°–2° in benign environments. Near motors, steel structures, or power lines, errors of ±5°–15° are possible.
2.1 Watson-Watt HF ADF Specifications
| Parameter | Specification |
|---|---|
| Frequency range | 1.8 MHz – 54 MHz (160M – 6M) |
| Array aperture (λ/4 spacing) | 3.5m (40M) to 81m (160M) |
| Element height | λ/4 monopole; shortened elements acceptable with loss of sensitivity |
| Number of elements | 4 (N, S, E, W) + 1 sense antenna |
| Feed line impedance | 50Ω coaxial |
| Balun type | Current-mode, FT-50-43 toroid, 10 turns trifilar |
| LNA gain | 40 dB (INA128, Rg = 3Ω) |
| ADC sample rate | 10 kHz |
| ADC block size | 512 samples |
| ADC bits | 12 (ESP32 SAR ADC) |
| Bearing update rate | ~20 Hz (one bearing per 512-sample block) |
| Bearing smoothing | 8-reading boxcar average |
| SNR threshold for valid bearing | 10 dB (configurable) |
| Bearing accuracy (good site, cal’d) | ±3°–5° RMS |
| Bearing accuracy (typical field) | ±5°–8° RMS |
| Ambiguity resolution | Sense antenna, cardioid phase comparison |
| WiFi SSID | RF-ADFHF |
| WiFi password | none (open AP) |
| WiFi IP | 192.168.4.1 |
| Bluetooth SPP device name | RF-ADFHFinder |
| Bluetooth baud rate | 115200 (SPP transparent) |
| Controller | ESP32-WROOM-32 |
| OLED | SSD1306 128×64, I2C address 0x3C |
| GPS | u-blox NEO-8M, UART2, 9600 baud |
| Compass | QMC5883L, I2C address 0x0D |
| Power input | 12V DC, 2.1mm center-positive barrel jack |
| Power consumption | 600 mA typical, 1A max (with LNA) |
| Operating temperature | -10°C to +55°C |
| Storage temperature | -20°C to +70°C |
| Dimensions (controller box) | 115mm × 65mm × 30mm (approx.) |
| Weight (controller + cable) | ~450g |
2.2 Doppler VHF/UHF ADF Specifications
| Parameter | Specification |
|---|---|
| Bands supported | 2M (144–148 MHz), 1.25M (222–225 MHz), 70cm (420–450 MHz), 33cm (900–928 MHz), 20cm (1240–1300 MHz) |
| Array geometry | 4 elements on circle, λ/2 diameter |
| Array diameter | 1027mm (2M) / 676mm (1.25M) / 330mm (70cm) / 163mm (33cm) / 115mm (20cm) |
| Array disk material | PETG or PLA printed disk, 3mm thickness |
| Element type | 5mm aluminum rod, λ/4 length |
| Element length by band | 480mm (2M) / 308mm (1.25M) / 156mm (70cm) / 77mm (33cm) / 55mm (20cm) |
| Switch IC | FST3253 (below 400 MHz), PE4259 cascaded (above 400 MHz) |
| Switching rate | 1000 Hz (2M/1.25M/70cm), 2000 Hz (33cm/20cm) |
| ADC sample rate | 10 kHz (I/F audio input) |
| Goertzel block size | 1024 samples |
| Bearing update rate | ~10 Hz |
| Bearing smoothing | 4-reading average |
| SNR threshold | 15 dB (FM discriminator output) |
| Bearing accuracy (SNR >30 dB) | ±0.5°–1° RMS |
| Bearing accuracy (SNR 20 dB) | ±1°–3° RMS |
| Bearing accuracy (SNR 10 dB) | ±5°–10° RMS |
| Signal type required | FM (voice, data, tone) |
| Ambiguity | None (full 0–360°) |
| WiFi SSID | RF-ADFVHF |
| Bluetooth device name | RF-ADFVHFinder |
| Controller | ESP32-WROOM-32 |
| Power input | 12V DC |
| Power consumption | 400 mA typical |
| Tripod mount | 1/4”-20 or 3/8”-16 female insert in disk |
2.3 Rotary Loop Specifications
| Parameter | Specification |
|---|---|
| Bands | 160M – 15M (1.8 – 21.5 MHz) |
| Loop shape | Square |
| Loop conductor | 4mm copper tube (or 4mm heavy copper wire) |
| Loop side length | 400mm |
| Loop perimeter | 1600mm |
| Loop inductance | ~1.45 µH (Wheeler formula) |
| Loop Q (air-core, HF) | 50–150 typical |
| Tuning method | Switched capacitor bank + varactor BB112 |
| Capacitance range | 39–5090 pF (all HF bands) |
| Tuning voltage | 0–3.3V DAC (ESP32 internal DAC) |
| Faraday shield | Copper foil tape, 10mm gap at top |
| Buffer amplifier | J310 JFET common-source, gain ~15 dB |
| Motor type | NEMA 17, 200 steps/rev, 1.5A, 12V |
| Motor driver | DRV8825, 1/16 microstepping |
| Gear reduction | 3:1 GT2 belt, 20T motor pulley, 60T loop pulley |
| Step resolution | 0.0375°/step (1/16 microstep) |
| Coarse scan step | 2° |
| Fine scan step | 0.25° |
| Full scan time (coarse) | ~3 minutes |
| Position feedback | AS5048A absolute magnetic encoder (12-bit, 0.088°/count) or potentiometer |
| Sense antenna | Short vertical whip, relay-switched, RC phase shift |
| Null depth (free space) | >30 dB (ideal) |
| Null depth (field) | 20–25 dB typical |
| Bearing accuracy | ±2°–5° (field conditions) |
| WiFi SSID | RF-ADFLOOP |
| Bluetooth device name | RF-ADFLOOPFinder |
| Power input | 12V DC |
| Power consumption | 800 mA (motor running), 300 mA (idle) |
| Dimensions (base plate) | 200mm × 200mm |
| Mass (complete system) | ~1.2 kg |
2.4 GPS/Compass Subsystem Specifications
| Parameter | Specification |
|---|---|
| GPS module | u-blox NEO-8M (or NEO-6M pin-compatible) |
| GPS interface | UART, 9600 baud, 3.3V TTL |
| GPS protocol | NMEA 0183 v4.10 |
| GPS NMEA sentences | GGA, RMC, GSA (parsed); others ignored |
| GPS position accuracy | 2.5m CEP (without SBAS) / 1.5m CEP (with SBAS) |
| GPS cold start TTFF | <60 seconds (typical) |
| GPS hot start TTFF | <2 seconds |
| GPS PPS output | 1Hz, 3.3V TTL, rising edge on second |
| PPS accuracy | ±30 ns (locked) |
| GPS antenna | Active patch, 28 dBi typical, 3.3V LNA bias via 33Ω series resistor |
| Compass module | QMC5883L |
| Compass interface | I2C, 400 kHz, address 0x0D |
| Compass field range | ±8 Gauss full-scale (±2G selected) |
| Compass sensitivity | 3000 LSB/Gauss (±2G range) |
| Compass output rate | 200 Hz (OSR 512, 10 Hz effective after averaging) |
| Compass heading accuracy | ±1°–2° (after hard-iron cal, level) |
| Compass heading accuracy (tilt) | ±2°–4° (tilt-compensated with MPU6050) |
| Declination model | Simplified IGRF, CONUS table, ±1° accuracy |
| I2C bus | SDA GPIO21, SCL GPIO22, 3.3V, 4.7kΩ pull-ups |
# CHAPTER 3 — CONTROLS AND INDICATORS
3.1 Watson-Watt HF ADF Controls
Front Panel Layout
The controller front panel provides:
- SSD1306 OLED display (128×64 pixels, 4 text lines plus status icons)
- Rotary encoder with push switch (band select and menu navigation)
- Power LED (green, 3mm, connected to 3.3V rail via 1kΩ)
- Bluetooth status LED (blue, 3mm, driven by firmware GPIO)
OLED Display Layout
Line 1 (top): [MODE] [BAND] e.g., "ADF-HF 40M"
Line 2: BRG: 247°M (236°T)
Line 3: SNR: 18.3dB CONF:HI
Line 4 (bottom): GPS:3D CMPS:OK BT:ON
| Line | Content | Notes |
|---|---|---|
| Line 1 | Operating mode and active band | MODE = ADF-HF, ADF-PA (phased array), or SCAN |
| Line 2 | Magnetic bearing and true bearing | True bearing = magnetic + declination |
| Line 3 | Signal SNR in dB and confidence level | Confidence: HI (>20dB), MED (10–20dB), LO (<10dB), INVALID |
| Line 4 | GPS fix status, compass status, BT status | GPS: NO FIX, 2D, 3D; CMPS: OK, CAL, ERR; BT: OFF, ON |
Rotary Encoder Functions
| Action | Function |
|---|---|
| Rotate clockwise | Step up through band table |
| Rotate counterclockwise | Step down through band table |
| Short press (<1 second) | Enter manual bearing offset entry mode |
| Long press (>2 seconds) | Enter main menu |
| Double-click | Toggle between ADF-HF and PHASED ARRAY mode |
LED Indicators
| LED | State | Meaning |
|---|---|---|
| Power (green) | Solid on | 3.3V rail present, ESP32 running |
| Power (green) | Blinking 1 Hz | Initializing (first 5 seconds after power-up) |
| Power (green) | Off | No power |
| Bluetooth (blue) | Off | Bluetooth not connected |
| Bluetooth (blue) | Solid on | Bluetooth SPP session active |
| Bluetooth (blue) | Blinking 2 Hz | Data being transmitted or received |
| Bluetooth (blue) | Blinking 0.5 Hz | Advertising, waiting for connection |
Band Table (Watson-Watt HF)
| Band Index | Band Name | Frequency | Element Length (λ/4) | Array Baseline |
|---|---|---|---|---|
| 0 | 160M | 1.85 MHz | 40.5m | 40.5m |
| 1 | 80M | 3.65 MHz | 20.5m | 20.5m |
| 2 | 60M | 5.33 MHz | 14.1m | 14.1m |
| 3 | 40M | 7.15 MHz | 10.5m | 10.5m |
| 4 | 30M | 10.12 MHz | 7.4m | 7.4m |
| 5 | 20M | 14.2 MHz | 5.3m | 5.3m |
| 6 | 17M | 18.1 MHz | 4.1m | 4.1m |
| 7 | 15M | 21.2 MHz | 3.5m | 3.5m |
| 8 | 12M | 24.9 MHz | 3.0m | 3.0m |
| 9 | 10M | 28.5 MHz | 2.6m | 2.6m |
| 10 | 6M | 50.5 MHz | 1.5m | 1.5m |
3.2 Doppler VHF ADF Controls
OLED Display Layout (VHF Mode)
Line 1: ADF-VHF 2M 146.520
Line 2: BRG: 127°M (116°T)
Line 3: RSSI: -82dBm SNR:22dB
Line 4: GPS:3D CMPS:OK BT:ON
| Line | Content |
|---|---|
| Line 1 | Mode, band, and frequency in MHz |
| Line 2 | Magnetic and true bearing |
| Line 3 | RSSI from AD8307 in dBm, and SNR estimate |
| Line 4 | GPS/compass/BT status (same as HF system) |
Web Interface (VHF)
The VHF web interface provides:
- SVG compass rose with bearing needle (auto-refreshed via fetch() every 500ms)
- Band selection buttons (2M, 1.25M, 70cm, 33cm, 20cm)
- RSSI bar graph
- Signal quality indicator (Excellent / Good / Fair / Weak)
- JSON data endpoint at GET /api/status
3.3 Rotary Loop Controls
OLED Display Layout (Loop Mode)
Line 1: ADF-LOOP 40M 7.150
Line 2: POS: 183.5° NULL: 047°
Line 3: DEPTH: 22dB CONF:MED
Line 4: GPS:3D MOTOR:IDLE BT:ON
| Line | Content |
|---|---|
| Line 1 | Mode, band, frequency |
| Line 2 | Current motor position and last-found null bearing |
| Line 3 | Null depth in dB and confidence level |
| Line 4 | GPS status, motor status (SCAN/IDLE/HOMING), BT status |
Web Interface (Loop)
The loop web interface provides:
- SVG compass rose with needle and superimposed figure-8 pattern overlay showing loop orientation
- SCAN button: start full 360° scan
- STOP button: halt scan at current position
- HOME button: return loop to 0° reference position
- TUNE button: start auto-tune sequence (find varactor setting for peak RSSI)
- SENSE ON / SENSE OFF toggle: enable/disable sense antenna
- Band selector grid
- Current position readout and null bearing readout
3.4 CYD Touchscreen (All Systems)
The CYD (ESP32-2432S028R) acts as a Bluetooth SPP master and connects to the nearest available ADF device. It provides a 320×240 pixel color touchscreen display with four operating modes selected by a tab bar at the bottom of the screen.
Tab Bar
[ CMPS ] [ MAP ] [ BAND ] [ SET ]
MODE_COMPASS
Full-screen compass rose, 280-pixel diameter, with:
- Compass rose ring showing cardinal and intercardinal points
- Bearing needle (red, 3-pixel width) pointing to bearing
- True North indicator (blue, N label)
- Magnetic North vs. True North offset indicator (small arc, labeled with declination value)
- Signal strength arc (green arc from 0° to scaled RSSI angle, at inner radius)
- Center display: bearing value in degrees, confidence level, band name
- Background: dark gray (#1A1A1A)
- Rose ring: white (#FFFFFF)
- Bearing needle: red (#FF2020)
MODE_MAP
Reserved for future implementation. Current display: “MAP VIEW — FUTURE FEATURE” with GPS coordinates and last bearing.
MODE_BANDS
Grid of band buttons, 11 HF + 5 VHF, arranged 4 per row:
[160M] [80M ] [60M ] [40M ]
[30M ] [20M ] [17M ] [15M ]
[12M ] [10M ] [6M ] [ ]
[2M ] [1.25M][70cm] [33cm]
[20cm] [ ] [ ] [ ]
Active band highlighted in yellow (#FFFF00) on dark background. Inactive bands in gray. Touch to select band (sends BAND n command via BT).
MODE_SETTINGS
Settings screen providing:
- BT target selector: list of discovered ADF devices, tap to connect
- Declination override: ±30° range, +/- buttons
- Display brightness slider: 25%, 50%, 75%, 100%
- Compass calibration start button (sends CAL START to connected ADF)
- Software version display
- WiFi IP address of connected ADF device
3.5 WiFi Web Interface
All three ADF systems host a WiFi access point and HTTP server. The web interface is served as a single HTML page with embedded JavaScript and SVG.
Compass Rose SVG
The compass rose is rendered as a scalable vector graphic (SVG) with:
- Outer ring with degree markings every 10°, labels every 30°
- Cardinal point labels (N, E, S, W) in larger font
- Intercardinal labels (NE, SE, SW, NW) in medium font
- Bearing needle: a red triangle rotated to the current bearing
- Center hub circle
- Optional second needle for true North offset
JavaScript Auto-Refresh
The bearing display updates without page reload using:
setInterval(function() {
fetch('/api/bearing')
.then(response => response.json())
.then(data => {
updateCompassNeedle(data.bearing_mag);
document.getElementById('brg_text').textContent =
data.bearing_mag.toFixed(1) + '° M (' +
data.bearing_true.toFixed(1) + '° T)';
document.getElementById('snr_text').textContent =
'SNR: ' + data.snr.toFixed(1) + ' dB';
});
}, 500);HTTP API Endpoints
| Endpoint | Method | Request Body | Response | Description |
|---|---|---|---|---|
| / | GET | none | HTML page | Main web interface |
| /api/bearing | GET | none | JSON | Current bearing (HF/loop) |
| /api/status | GET | none | JSON | Full status (all systems) |
| /api/band | POST | {“band”: n} | JSON ack | Set active band |
| /api/cal | POST | {“action”: “start”/“end”} | JSON ack | Compass calibration |
| /api/decl | POST | {“decl”: f} | JSON ack | Set declination override |
| /api/scan | POST | {“action”: “scan”/“stop”/“home”} | JSON ack | Loop motor control |
| /api/tune | POST | none | JSON ack | Start loop auto-tune |
JSON Response Format (GET /api/status)
{
"bearing_mag": 247.3,
"bearing_true": 236.8,
"snr_db": 18.3,
"rssi_dbm": -82.1,
"confidence": "HIGH",
"band": "40M",
"frequency_mhz": 7.15,
"gps_lat": 37.3022,
"gps_lon": -120.4830,
"gps_fix": "3D",
"gps_sats": 9,
"compass_heading": 358.7,
"declination": -13.5,
"uptime_s": 1847,
"bt_connected": true,
"fw_version": "1.0.0"
}4.1 Watson-Watt Array Deployment
Site Selection
The quality of a Watson-Watt array site has a major impact on bearing accuracy. Select sites that meet the following criteria:
- Flat terrain within a radius of at least 2× the array aperture in all directions.
- No metallic fences, pipes, towers, vehicles, or structures within 2× the array aperture.
- No power lines overhead or within 1× the aperture (power line hum induces common-mode interference).
- Consistent ground conductivity (avoid half-ground, half-pavement splits within the array).
- Clear sky view for GPS (not required for RDF function, but needed for positioning).
Conduct a site survey by rotating a portable receiver antenna and listening for signal reflections or sudden bearing shifts. If more than ±5° bearing variation is observed while slowly walking around the site perimeter, select a different site.
Element Alignment
- Identify magnetic North with the system compass. Note the current declination value displayed by the firmware.
- Drive the center stake at the selected center point.
- Using a compass (corrected for declination to get true North/South), pace out the array baseline distance in each cardinal direction.
- Drive the N, S, E, and W stakes at these positions. Verify alignment: N-S pair must be co-linear; E-W pair must be co-linear and perpendicular to N-S.
- Stake alignment error: each element must be within ±2° of its nominal bearing from center, and within ±5% of its nominal distance from center.
Element Installation
- Attach the printed stake cup to the top of each driven ground stake.
- Insert the element rod (aluminum, 6mm diameter, length per band table) into the stake cup.
- Tighten the M3 set screw to secure the rod. The rod must be vertical: check with a small level.
- For telescoping elements, extend to the required length and tighten the locking collar.
- Connect the coax feed line to the SO-239 connector at the element base. Dress the coax to prevent it from touching the element rod.
Feed Line Phase Matching
Feed lines from each element to the central junction box must be cut to identical electrical length.
- Cut five lengths of RG-58 coax (4 element lines + 1 sense line). Starting length: add 20% to the measured physical distance from the farthest element to the center box.
- Using a NanoVNA, measure the electrical length (phase of S11 with end open) for each piece of coax.
- Trim the longer pieces until all four element lines match within ±5°.
- Do not bend the coax sharply (minimum bend radius: 50mm for RG-58).
- Route feed lines radially from the center, lying flat on the ground. Secure with cable stakes every 2m.
- Label all coax: N, S, E, W, SENSE at both ends with heat-shrink labels.
Junction Box
- Place the junction box at the array center, next to the center ground stake.
- Connect element coax cables to the N, S, E, W, and SENSE inputs as labeled on the PCB.
- Connect the output coax (to controller box) to the single SO-239 output connector.
- Bond the junction box metal enclosure to the center ground stake with a short #12 AWG ground wire.
- Route controller output coax to the controller box location. This cable may be any length up to 10m without phase concerns (it carries the single-ended combined signal).
Phase Matching Verification
- Connect the NanoVNA CH0 port to the junction box output.
- Place a short-circuit jumper at the far end of the North element coax.
- Note the phase angle displayed at the operating frequency.
- Repeat for South, East, and West elements.
- Phase must agree within ±5° for all four elements.
- If phase mismatch exceeds ±5°, re-trim or replace the offending coax.
4.2 Doppler Ring Array Mounting
Printed Disk
The Doppler ring disk is a printed part (PETG or PLA, 3mm thickness) with the following features:
- Central 1/4”-20 female brass insert (for tripod mounting)
- Optional 3/8”-16 adapter insert
- 4 element posts at 90° intervals at the correct diameter for the selected band
- Element posts: M5 female thread, 20mm depth
- Center coax routing channel: 25mm diameter bore for RG-316 or RG-174
- Switch PCB mounting boss: 4× M2.5 standoffs in center
Tripod and Mast Installation
- Thread the tripod head stud (1/4”-20) into the disk center insert and tighten snugly.
- Insert a 1-inch (25mm) aluminum tube through the center bore, extending 1m above the disk.
- Secure the tube with the mast clamp printed onto the disk underside.
- Elevate the assembled disk to at least 1m above the tripod apex (total height 2–3m above ground).
- Level the disk: use a small bubble level placed on the disk surface. Adjust tripod legs until level.
- Orient element 1 (labeled “N”) toward magnetic North using a compass.
Element Installation
- Screw the element rod (5mm aluminum) into the element post, threading it 10mm into the post for secure seating.
- Element rod lengths (pre-cut, one set per band):
| Band | Element Length | Total with post |
|---|---|---|
| 2M | 480mm | 500mm |
| 1.25M | 308mm | 328mm |
| 70cm | 156mm | 176mm |
| 33cm | 77mm | 97mm |
| 20cm | 55mm | 75mm |
- All four elements in a set must be the same length. Verify with a steel rule before installation.
Switch PCB and Coax
- Install the switch PCB (FST3253-based) on the center boss standoffs. Secure with M2.5 × 6mm screws.
- Connect the four element coax stubs (50Ω, 30mm each) from the PCB to the element post SMA connectors.
- Route the RG-316 output coax down through the center mast tube to the receiver.
- At the receiver, terminate in the appropriate connector (BNC for most receivers, SMA for SDRs).
- Connect the switch control cable (4-wire, to ESP32 GPIOs for switch select lines) from the PCB down the mast exterior. Secure with cable ties every 300mm.
Elevation
For best VHF/UHF performance, the array should be elevated to at least 1m above surrounding vegetation and terrain. A height of 2–3m is preferred. At low elevation, ground reflections can degrade bearing accuracy by ±5°–10°.
4.3 Rotary Loop Installation
Base Mount
- Set the base mount (printed or machined, 200mm × 200mm) on a level, stable surface (table, plywood sheet on ground, or tripod via M8 adapter bolt).
- Verify level with a bubble level in two perpendicular orientations. Adjust table or shim base until level within ±1°.
- Secure the base to the surface with at least two M5 screws or C-clamps.
Motor Installation
- Align the NEMA 17 motor (flange side up) with the 4 mounting holes on the underside of the base plate.
- Insert M3 × 10mm socket-head screws through the base and thread into the motor flange.
- Tighten evenly in a cross pattern. Final torque: 0.5 N·m (5 kgf·cm).
- Verify the motor shaft protrudes upward through the base center bore.
- Press the 20T GT2 pulley onto the motor shaft, key engaged. Tighten M3 set screw with shaft flat engaged.
Loop Frame and Shaft
- Construct the square loop from 4mm copper tube. Cut four 400mm sections and four 90° elbows.
- Solder all joints using electrical (non-plumbing) solder and flux. Ensure all joints are electrically and mechanically sound.
- Verify electrical continuity around the loop with a DMM: resistance should be less than 0.5Ω.
- Mount the loop shaft adapter bracket to the top center of the loop using M3 stainless screws and lockwashers.
- Thread the shaft (8mm aluminum, 150mm length) through the bearing block in the base plate center.
- Press the 60T GT2 pulley onto the loop shaft, and align with motor pulley. Tighten M3 set screw.
- Install the GT2 belt (300mm) around both pulleys. Belt tension: deflect 5mm with light finger pressure at mid-span.
Faraday Shield
A Faraday shield (electrostatic shield) prevents capacitively-coupled electric field interference from contaminating the magnetic field measurement. The shield must be open (not a closed conductor) to avoid short-circuiting the magnetic flux.
- Wrap copper foil tape (25mm width) around the outer surface of the copper tube loop.
- Begin wrapping at the center of the bottom rail of the loop. Wrap completely around the three remaining sides.
- Leave a 10mm gap at the top center of the top rail. This gap prevents the shield from acting as a shorted single-turn loop that would cancel the loop’s signal.
- Ensure the foil tape is continuous (no gaps other than the intentional top gap) and well-adhered to the copper tube.
- Solder a ground wire from one end of the shield to the common ground of the buffer amplifier PCB.
Buffer Amplifier
- Fabricate or obtain the J310 JFET buffer amplifier PCB (see schematic in firmware repository).
- Mount the PCB in the weatherproof enclosure at the base of the loop, near the feed point.
- Connect the loop feed terminals to the amplifier input using two short (50mm) leads.
- Ensure the input connections are balanced (equal capacitance to ground from each feed terminal to prevent conversion of electric field pickup to differential voltage).
- Connect the amplifier output to a length of RG-316 coax, routed down through the hollow shaft center bore to the controller box.
- The RG-316 coax inner conductor may be used to supply 12V DC to the buffer amplifier (bias tee arrangement: 33Ω + 47µH RF choke, 100µF bypass on amplifier end).
AS5048A Position Encoder (Optional)
If using the AS5048A absolute magnetic encoder for loop position:
- Attach the diametrically magnetized magnet to the top of the loop shaft (center of shaft face, adhesive + M3 screw).
- Mount the AS5048A PCB on a standoff 2mm above the magnet surface, centered. Secure with M2 screws.
- Connect I2C lines (SDA, SCL) and 3.3V/GND to the ESP32.
- Verify operation: in firmware monitor, issue STATUS command and confirm position reads 0°–360° without discontinuity through full rotation.
4.4 Controller Box Installation
Desktop Installation
- Place the controller box on a stable, level surface within 1m of the antenna or coax feed point.
- Ensure minimum clearance of 100mm on all sides for ventilation.
- Connect the RF coax from the antenna/junction box to the RF IN connector on the controller front panel.
- Connect the GPS antenna (active patch) to the GPS SMA connector. Route antenna for clear sky view.
- Connect the compass module (if external) to the I2C header on the rear panel.
- Connect power: 12V DC, 2.1mm center-positive barrel connector. Power supply rating: minimum 1.5A.
Cable Routing
- Route RF coax away from motor lead cables, switch control cables, and power cables. Minimum separation: 100mm. Cross at 90° when unavoidable.
- GPS antenna cable: keep away from RF coax and motor cables. GPS cable may run parallel to power cable.
- Motor cable (loop system): use shielded cable (overall braid shield, grounded at controller end only). Unshielded motor cables radiate significant RFI.
- Tie cables with cable ties. Do not over-tighten (maximum loop stress: 10N on coax).
GPS Antenna Placement
- The active GPS patch antenna must have a clear view of the sky. Best placement: horizontal surface outdoors, facing up.
- A minimum of 120° unobstructed view cone is required for a reliable 3D GPS fix.
- Do not place the GPS antenna near metal surfaces (roofs, vehicles) that may block signals or cause multipath.
- The GPS antenna cable may be extended up to 5m with RG-316 without significant signal loss. The LNA bias voltage (3.3V via 33Ω) must remain above 2.5V at the antenna end; verify with DMM if extension cable is used.
Compass Orientation
- The QMC5883L sensor must be oriented with its X-axis pointing toward the front of the controller (the side with the RF IN connector and OLED display).
- If the compass is mounted on the ESP32 carrier PCB, ensure the PCB is mounted horizontally in the enclosure with the X-axis label aligned with the front of the enclosure.
- If the compass is on a remote header (for use away from the motor and RF components), the same X-axis orientation applies.
- Note and mark the compass orientation on the enclosure exterior with a small arrow label.
4.5 Power Requirements
Voltage and Current
| System | Nominal Voltage | Typical Current | Peak Current | Minimum Supply |
|---|---|---|---|---|
| Watson-Watt HF | 12V DC | 600 mA | 1.0 A | 12V, 1.5A |
| Doppler VHF | 12V DC | 400 mA | 700 mA | 12V, 1.0A |
| Rotary Loop | 12V DC | 300 mA (idle) | 800 mA (motor) | 12V, 1.5A |
Power Connector
All systems use a 2.1mm center-positive barrel jack (industry standard). The power supply must be regulated (ripple <100 mV at full load).
Internal Regulation
The controller PCB includes: - AMS1117-3.3 LDO (3.3V, 800 mA) for ESP32, OLED, compass, GPS - 78L05 (5V, 100 mA) for optional logic-level accessories - Motor driver (loop system) powered directly from 12V rail
Power Sequencing
WARNING: Always follow this power-up sequence to avoid damage or false readings:
- Verify all coax and antenna connections are made before applying power.
- Apply 12V DC to the controller box. Wait for OLED splash screen.
- For the loop system: do not move the loop manually while power is applied.
- For the HF system: do not transmit RF into the array feed while the system is powered (receive only).
- Do not use the USB micro-B connector for operational power. The USB port is for programming only. Operating the ESP32 from USB power while the 12V supply is also connected may cause damage to the AMS1117 regulator.
# CHAPTER 5 — OPERATING PROCEDURES
5.1 System Power-Up (All Systems)
Perform the following steps for any ADF system:
- Verify all RF coax connections are secure. Check that coax connectors are fully seated and finger-tight. No open coax ends should be exposed.
- Verify GPS antenna has clear sky view and is connected to the GPS SMA port.
- Apply 12V DC power. The OLED will illuminate within 1 second with the splash screen: “ADF SUITE v1.0 / TM-ADF-001”.
- Loop system only: The motor will home the loop to the 0° reference position. This takes approximately 5 seconds. Do not obstruct the loop during homing.
- After 3 seconds, the OLED switches to the operational display. The current mode and band are shown on Line 1.
- The WiFi AP broadcasts within 5 seconds. SSID: “RF-ADF[type]” (e.g., “RF-ADFHF”). The AP is open (no password). IP address: 192.168.4.1.
- Connect a phone or laptop to the WiFi AP. Open a browser and navigate to http://192.168.4.1 to access the web interface.
- The Bluetooth SPP adapter begins advertising. Use a Bluetooth terminal app (e.g., Serial Bluetooth Terminal for Android) to connect to “RF-ADF[type]Finder”. Baud rate setting is not applicable for SPP.
- The CYD display (if present) powers on independently. It scans for and connects to the nearest ADF Bluetooth device automatically. When connected, the BT LED on the CYD flashes once and then stays steady.
- GPS fix acquisition begins immediately. Cold start requires up to 60 seconds for first fix. OLED Line 4 shows “GPS:SRCH” until acquired, then “GPS:2D” or “GPS:3D”.
- Compass calibration required on first use, or after transporting the system to a new location. Proceed to Section 5.2.
5.2 Compass Calibration
Perform compass calibration at initial deployment and after any significant change in the operating environment (new location, new enclosure materials, or suspected ferrous material changes near the compass).
- Issue the calibration start command via Bluetooth or web interface:
CAL START - The OLED shows “CAL MODE — ROTATE SLOWLY”. The bearing display suspends.
- Slowly rotate the entire controller assembly (the compass must move with it) through two complete 360° rotations. Rotate about the vertical axis. Keep the unit level during rotation.
- Rotation speed: approximately 60 seconds for two complete rotations (30°/second). Do not rotate faster; rapid rotation may cause the magnetometer to report erroneous values due to settling time.
- Move away from any large metal objects, motors, or electronic equipment during calibration.
- After completing the rotations (approximately 60 seconds), issue:
CAL END - The firmware computes hard-iron offsets (X_offset, Y_offset) and saves them to ESP32 NVS (Non-Volatile Storage). NVS is retained through power cycles.
- The OLED returns to the normal operational display. Line 4 shows “CMPS:OK” if calibration succeeded.
- Verify calibration: point the system toward a known compass bearing (use a baseplate compass as a reference). The OLED magnetic heading should agree with the reference within ±3°.
- If agreement is worse than ±5°, suspect magnetic interference in the immediate environment or a compass sensor failure. Check Section 9.4 Fault Codes.
5.3 Watson-Watt Bearing Acquisition
Prerequisites: Array deployed and aligned per Section 4.1. System powered up and GPS acquired.
- Deploy the Adcock array per Section 4.1. Verify all four feed lines are connected and labeled.
- Apply power per Section 5.1.
- Select the band of the target signal. Use the encoder: rotate to the
desired band on the OLED Line 1 display. Or, via Bluetooth:
BAND 3(for 40M, for example). Or, via the web interface: tap the 40M button. - Tune the connected receiver (external, connected to the junction box output) to the target frequency. The system does not tune a receiver; it processes the IF or audio output.
- Monitor the OLED Line 3 for SNR. Valid bearing requires SNR > 10 dB. If SNR is less than 10 dB, the signal is too weak or absent; the bearing display will show “INVALID” or dashes.
- When the target signal is present with sufficient SNR, the bearing is displayed automatically on Line 2: “BRG: XXX°M (YYY°T)”.
- Read the magnetic bearing (M) for use with a magnetic compass. Read the true bearing (T) for map plotting and triangulation.
- Observe the CONF indicator (Line 3): HI = high confidence; MED = moderate; LO = low; INVALID = no valid bearing.
- Apply magnetic declination to convert: TRUE = MAGNETIC + DECLINATION (East declination is positive). The firmware does this automatically; verify the displayed declination value is correct for your location.
- Record the bearing and GPS position. For triangulation, at least two positions are required (see Section 5.6).
- Take at least 3 readings spaced 30 seconds apart and average them to reduce site multipath effects.
5.4 Doppler VHF Bearing Acquisition
Prerequisites: Doppler ring array mounted and aligned per Section 4.2. Receiver connected to array output coax.
- Mount the Doppler ring array on tripod, 2–3m above ground. Orient element 1 (N) toward magnetic North.
- Apply power to the controller per Section 5.1.
- Select the correct band for the target frequency using encoder or
BAND ncommand. The firmware sets the switching rate and expects elements of the correct length for the selected band. - Tune the FM receiver to the target frequency. Connect the receiver discriminator audio output to the ESP32 ADC input (GPIO34) via the 3.5mm audio jack on the front panel. Maximum audio input level: 1 Vpp (attenuate if required).
- Set receiver squelch just below the target signal level. A fully-open squelch on a noisy band will produce random bearings.
- Monitor OLED Line 3 RSSI. Valid bearing acquisition typically requires RSSI > -95 dBm for the 2M band.
- The bearing is displayed on Line 2 when the Goertzel algorithm detects a valid 1 kHz phase reference. Typical bearing update rate: 10 Hz.
- The Doppler system gives a direct magnetic bearing, no ambiguity resolution needed. The bearing is continuously updated as long as the signal is present.
- For best accuracy, wait for at least 5 seconds of stable readings before recording. Bearing jitter (standard deviation) with good signal is typically < ±2°.
- Record bearing and GPS position for triangulation.
5.5 Rotary Loop RDF Operation
Prerequisites: Loop system assembled and installed per Section 4.3. System powered up per Section 5.1.
- Level the loop base on a stable surface. Verify motor home position (loop at 0° = North reference mark on shaft coupling).
- Select the operating band:
BAND nor use encoder. The firmware sets the correct varactor DAC voltage range for the selected band. - Issue the TUNE command:
TUNE(or tap TUNE button on web interface). The firmware sweeps the varactor voltage from minimum to maximum, measuring RSSI at each step via the AD8307 detector, and sets the varactor to the voltage that produces maximum RSSI. This auto-tune process takes 3–8 seconds depending on band. - Issue the SCAN command:
SCAN(or tap SCAN button). The motor begins rotating the loop at 2°/step with 100ms settle time per step. A full 360° scan takes approximately 3 minutes. - During the scan, the OLED updates with the current motor position (Line 2) and the best null candidate bearing found so far.
- At scan completion, the firmware displays the null bearing on Line 2 and the null depth on Line 3.
- Verify null depth is > 15 dB. If null depth is less than 15 dB, the bearing may be unreliable. Proceed with caution.
- If null depth is > 15 dB, resolve the 180° ambiguity with the
sense antenna:
- Issue:
SENSE ON - The firmware momentarily rotates the loop to the null bearing, then adds the sense antenna signal.
- The OLED displays the resolved bearing (single 0–360° value) and marks it with “(SENSE)”.
- Issue:
SENSE OFFafter recording the bearing.
- Issue:
- The transmitter is in the direction of the resolved bearing ± the bearing accuracy of the system.
- Record the bearing and GPS position.
5.6 Multi-Point Triangulation
Multi-point triangulation determines the location of a transmitter from bearings taken at two or more known positions.
At location 1: Record:
- GPS position: lat/lon in decimal degrees
- True bearing to transmitter: θ₁ (true, from any ADF system)
- Time and date
- Confidence level and SNR
Move to location 2. Minimum separation from location 1: 500m. For best geometry, location 2 should be positioned such that the angle between bearing lines at the estimated transmitter location is between 60° and 120°. Avoid geometry where the two bearing lines are nearly parallel (cross-angle < 30°) as this produces very large position error.
At location 2: Record the same data as at location 1.
Optionally, obtain a third bearing from a third location. A third bearing dramatically reduces position uncertainty (converts a line-segment intersection to a triangle of error whose centroid is the best-estimate transmitter location).
Plot the bearing lines on a map (paper or digital):
- From each location, draw a line at the measured true bearing, extending in the direction of the transmitter.
- The lines intersect at or near the transmitter location.
For digital triangulation, use the web interface MAP view (future feature) or a navigation formula:
Given: (lat1, lon1), θ₁ and (lat2, lon2), θ₂ Convert bearings to unit direction vectors in ECEF Find intersection of the two great-circle bearing linesPosition accuracy of the triangulation depends on:
- Bearing accuracy at each point (±5° bearing error propagates to position error proportional to distance)
- Geometry (cross-angle near 90° is best)
- Distance to the transmitter (longer range = larger position error for the same bearing error)
Rule of thumb: position error ≈ bearing_error_km = range × tan(bearing_rms) where bearing_rms is in radians.
For a transmitter at 10 km range and bearing accuracy of ±5° (0.087 rad): position error ≈ 10 × 0.087 = 870m.
# CHAPTER 6 — CALIBRATION PROCEDURES
6.1 Watson-Watt Balance Calibration
Equipment Required
- Signal source: HF transceiver or signal generator, 1W output minimum at 50Ω
- Reference antenna: 50Ω dummy load with tap for transmitting, or an outdoor antenna oriented at a known bearing
- GPS for precise distance and bearing measurement
- NanoVNA for feed line phase verification (optional but recommended)
Procedure
- Place the calibration transmitter at a measured bearing from the array center. Best calibration bearing: 45° (NE). This excites both the N-S and E-W channels nearly equally, giving the most sensitive balance indication.
- Measure the bearing from array center to transmitter precisely using GPS coordinates and the Haversine formula (see Section 1.6). Record this as θ_ref.
- Apply power to the ADF system and select the appropriate band.
- Issue:
STATUSvia Bluetooth. The system reports raw values: V_NS, V_EW, and the computed bearing θ_computed. - Compare θ_computed to θ_ref. If they agree within ±3°, no adjustment is needed.
- If θ_computed differs from θ_ref by more than ±3°, adjust the
software calibration values:
- Compute the gain correction needed for each channel.
- Issue:
GAIN NS 1.05(or the required ratio) to scale the N-S channel. - Issue:
GAIN EW 0.97to scale the E-W channel. - Re-check bearing.
- Test at four bearings: 45°, 135°, 225°, 315°. All four should agree with reference within ±5°.
- If systematic bearing error is present at all azimuths (rotating
offset), issue:
OFFSET dd.dwhere dd.d is the correction in degrees (positive = add to computed bearing). - Issue:
CAL SAVEto write calibration values to NVS. Values are retained through power cycles.
6.2 Doppler Phase Calibration
Procedure
- Place the calibration transmitter (FM, tone on carrier or voice) at true North from the array center (or any known bearing; North is most convenient).
- Compute the magnetic bearing equivalent: Magnetic_North = 0° + declination (e.g., if declination is -13.5°, magnetic bearing to true North = 346.5°).
- Issue:
CAL STARTvia Bluetooth. - Note the bearing displayed. The firmware records this bearing and the difference from the known bearing as a phase correction.
- Move the transmitter to the East position (or known bearing 90° from the first position).
- Issue:
CAL ADD 90to register a second calibration point. - Repeat for South and West.
- Issue:
CAL END. The firmware computes the best-fit phase offset from the four calibration points and saves it to NVS. - Verify: place transmitter at a new bearing (e.g., 127°). Confirm measured bearing agrees within ±2°.
6.3 Loop Null Depth Verification
Purpose
The null depth of the loop antenna is a critical quality indicator. Insufficient null depth indicates a construction defect (loop asymmetry, shield gap closed, or feed point imbalance) that will degrade bearing accuracy.
Procedure
- Place a signal source (HF transmitter, 1W) at a known position.
- Rotate the loop until the signal is at maximum on the AD8307 detector output. Note the position (this should be approximately when the loop plane faces the transmitter — loop axis approximately parallel to the signal direction).
- Record the RSSI at this maximum position. Example: -60 dBm.
- Issue:
MOVE +90to rotate the loop 90° from the peak position. This places the loop in the null orientation (loop plane edge-on to the transmitter). - Wait for the motor to settle (2 seconds). Record the new RSSI. Example: -82 dBm.
- Compute null depth: null_depth = RSSI_peak - RSSI_null = (-60) - (-82) = 22 dB.
- Evaluate:
- Null depth > 25 dB: Excellent. Bearing accuracy better than ±3°.
- Null depth 15–25 dB: Acceptable. Bearing accuracy ±3°–5°.
- Null depth 10–15 dB: Marginal. Bearing accuracy ±5°–10°. Investigate cause.
- Null depth < 10 dB: Unacceptable. Do not use for RDF until repaired.
- If null depth is inadequate, check:
- Faraday shield gap is open (10mm gap at top).
- No accidental conductor across the gap.
- Feed point connections are symmetrical (equal capacitance to ground from each terminal).
- Loop joints are all soldered and making good electrical contact.
- Buffer amplifier ground is correctly connected.
6.4 GPS/Compass Cross-Check
Procedure
- Drive or walk in a straight line over at least 100m. Record the GPS track bearing from the GPS RMC sentence ($GPRMC track angle field, degrees true).
- Record the compass heading displayed on the ADF system OLED (magnetic heading, Line 4 area).
- Convert compass heading to true: True_heading = Compass_heading + Declination.
- Compare GPS track bearing (true) to compass true heading. They should agree within ±3°.
- If the discrepancy exceeds ±5°, perform the following checks:
- Re-run compass calibration (Section 5.2).
- Verify the displayed declination value is correct for your location.
- Check for magnetic interference near the compass: move all motors, speakers, and ferrous tools away from the controller.
- Verify the GPS is reporting a valid track (requires movement; stationary GPS track bearing is meaningless).
- After repairs, repeat the cross-check.
# CHAPTER 7 — NEC ANTENNA MODELS
7.1 Available NEC Models
The following NEC2 model files are included in the
nec_models/ directory of the ADF Systems repository:
| Filename | Antenna Type | Frequency | Key Result |
|---|---|---|---|
| adcock_hf_40m.nec | 4-element Adcock array | 7.15 MHz | Impedance per element, azimuth figure-8 pattern |
| adcock_hf_80m.nec | 4-element Adcock array | 3.65 MHz | Element impedance, ground effect comparison |
| adcock_hf_20m.nec | 4-element Adcock array | 14.2 MHz | High-angle wave rejection verification |
| doppler_ring_2m.nec | 4-element ring array | 146 MHz | Per-element pattern, phase vs. azimuth |
| doppler_ring_70cm.nec | 4-element ring array | 435 MHz | Element mutual coupling, VSWR |
| loop_rdf_40m.nec | Single-turn square loop | 7.15 MHz | Figure-8 pattern, null depth, impedance |
| loop_rdf_80m.nec | Single-turn square loop | 3.65 MHz | Pattern, sensitivity at half-resonance |
| loop_with_shield_40m.nec | Loop + Faraday shield | 7.15 MHz | Shield effect on pattern purity |
| sense_ant_40m.nec | Short vertical monopole | 7.15 MHz | Omnidirectional pattern, impedance |
7.2 Running NEC Models
Software Requirements
Install xnec2c on Debian:
sudo apt install xnec2cFor command-line (batch) NEC runs, install nec2c:
sudo apt install nec2cRunning with xnec2c (GUI)
- Launch:
xnec2c filename.nec - In the menu, select Calculations → Run NEC to compute the model.
- View results:
- Calculations → Impedance: shows feedpoint impedance vs. frequency (if a frequency sweep is defined)
- Calculations → VSWR: shows VSWR referred to 50Ω
- Calculations → Radiation Pattern: opens the pattern display window
- Calculations → Current Distribution: shows current distribution along wires
Running with nec2c (CLI)
nec2c -i adcock_hf_40m.nec -o adcock_hf_40m.outParse the .out file for EN (impedance) and RP (radiation pattern) cards.
7.3 Interpreting the Adcock Array Model
Loading the Model
Open adcock_hf_40m.nec. The model contains: - 4 vertical
wire elements (N, S, E, W), each λ/4 = 10.5m long, 3mm radius - Ground
screen simulated with NEC perfect-ground option (GN 1 card) or
Sommerfeld ground (GN card with εᵣ = 13, σ = 0.005 S/m for average
ground) - Excitation: delta-gap voltage source at base of one element
(one at a time for pattern)
Impedance at Element Base
Run the single-element version first (N element only, others absent). Expected result at 7.15 MHz for a λ/4 monopole over average ground: - Input impedance: approximately 36 + j21 Ω (real part slightly above 36Ω free-space value due to ground loss)
Now run the full 4-element array with all elements present but only the N element excited. The impedance changes due to mutual coupling: - Expected: 32 + j18 Ω (mutual impedance lowers resistance slightly) - This value changes with element spacing; verify the model matches your actual spacing.
Azimuth Pattern: N-S Pair
Configure the model to excite the N element with +1V and the S element with -1V (differential excitation). View the azimuth pattern at zero elevation: - Expected: figure-8 pattern with maximum in N-S direction, nulls in E-W direction - Null depth in free space: >40 dB - Over real ground: null depth may be reduced to 25–35 dB
E-W Pair Verification
Repeat with E and W elements excited in differential mode. Pattern should be the mirror of the N-S pattern, rotated 90°.
7.4 Interpreting the Doppler Ring Model
Loading the Model
Open doppler_ring_2m.nec. The model contains: - 4
vertical monopoles (λ/4 = 480mm length, 2.5mm radius) arranged on a
circle of diameter 1027mm - Ground plane: horizontal radials or perfect
ground - Excitation: voltage source at base of each element, one at a
time
Per-Element Far-Field Phase
For each element (activated in turn), compute the far-field complex E-field in the direction θ = 0° (North). Record the phase of the E-field for each element:
| Active Element | Expected Phase at θ = 0° |
|---|---|
| Element 1 (North) | 0° |
| Element 2 (East) | +90° |
| Element 3 (South) | +180° |
| Element 4 (West) | +270° |
This confirms that as the array switches N → E → S → W, the received signal phase progresses from 0° to 90° to 180° to 270° for a transmitter to the North. The resulting temporal phase modulation is:
φ(t) = θ_transmitter × sin(2π × f_switch × t)
where f_switch = 1000 Hz, confirming the Doppler FM subcarrier mechanism.
VSWR and Mutual Coupling
Run frequency sweep from 140–150 MHz. Typical VSWR for the 2M array: <2:1 from 140–152 MHz with 5mm rod elements. Mutual coupling between adjacent elements typically reduces the loaded Q and broadens the VSWR bandwidth.
7.5 Pattern Optimization
Omnidirectional Element Pattern
For best Doppler and Watson-Watt RDF performance, each individual element should be as omnidirectional as possible in the azimuth plane. Departures from omnidirectional response (due to mutual coupling, nearby structures, or ground asymmetry) introduce bearing errors.
Watson-Watt: Verify each monopole’s azimuth gain variation is less than ±1 dB. Use the NEC model with real ground to check elevation-angle response; pattern should have maximum between 10° and 45° elevation (to reject low-angle skywave on HF).
Loop: In free space, the ideal loop figure-8 pattern
should have null depth > 30 dB. The NEC model
loop_rdf_40m.nec shows the expected null depth. Confirm: -
Null depth: >30 dB (free space model) - Reduction over real ground:
model with GN 2 card (Sommerfeld) at various soil conductivities -
Expected result: null depth 20–25 dB at 5m elevation over average
ground
Achieving Deep Nulls in Practice
To achieve the theoretical null depth in the NEC model with a physical loop: 1. Loop construction must be mechanically symmetric (all sides equal, corners square). 2. Feed point must be electrically symmetric (equal capacitance to ground from each terminal, equal lead length from loop to amplifier). 3. Faraday shield gap must be exactly at the electrical center of the top rail. 4. Buffer amplifier common-mode rejection must be > 40 dB.
# CHAPTER 8 — FIRMWARE ARCHITECTURE
8.1 File Structure
ant_gear/adf_systems/
├── firmware/
│ ├── esp32_adf_hf/
│ │ ├── esp32_adf_hf.ino Main sketch
│ │ ├── config.h GPIO, band tables, constants
│ │ ├── watson_watt.h WatsonWatt class
│ │ ├── bearing.h BearingDisplay class
│ │ ├── web_page.h Embedded HTML/JS (PROGMEM)
│ │ └── bt_handler.h Bluetooth SPP command parser
│ ├── esp32_adf_vhf/
│ │ ├── esp32_adf_vhf.ino Main sketch
│ │ ├── config.h VHF bands, switch pins, Doppler params
│ │ ├── doppler.h DopplerRDF class
│ │ ├── bearing.h (shared with HF, same file)
│ │ └── web_page.h VHF web interface HTML
│ ├── esp32_rotary_loop/
│ │ ├── esp32_rotary_loop.ino Main sketch
│ │ ├── config.h Motor pins, scan parameters
│ │ ├── loop_rdf.h LoopRDF class
│ │ ├── bearing.h (shared)
│ │ └── web_page.h Loop web interface HTML
│ └── cyd_adf_display/
│ └── cyd_adf_display.ino CYD single-file sketch
├── nec_models/ NEC2 model files
├── schematics/ KiCad schematic files
├── enclosures/ 3D print files (STL/STEP)
└── manual/
└── ADF_SYSTEMS_MANUAL.md This document
8.2 Watson-Watt HF Firmware (esp32_adf_hf)
config.h
Defines all hardware assignments and tunable parameters:
// GPIO assignments
#define PIN_RF_SWITCH_NS 25 // DPDT relay coil for N-S pair
#define PIN_RF_SWITCH_EW 26 // DPDT relay coil for E-W pair
#define PIN_SENSE_RELAY 27 // Sense antenna relay
#define PIN_ADC_INPUT 34 // ADC input from INA128
#define PIN_LED_POWER 2 // Green LED
#define PIN_LED_BT 4 // Blue BT LED
#define PIN_ENCODER_A 18 // Rotary encoder A
#define PIN_ENCODER_B 19 // Rotary encoder B
#define PIN_ENCODER_SW 5 // Encoder push switch
#define PIN_GPS_RX 16 // GPS UART RX
#define PIN_GPS_TX 17 // GPS UART TX
#define PIN_GPS_PPS 35 // GPS 1PPS interrupt
// ADC parameters
#define ADC_SAMPLE_RATE 10000 // Hz
#define ADC_BLOCK_SIZE 512 // samples per bearing computation
#define ADC_VREF 3.3f // Volts
// Algorithm constants
#define SNR_THRESHOLD_DB 10.0f // Minimum SNR for valid bearing
#define BEARING_HISTORY 8 // Readings for boxcar average
#define SENSE_PHASE_THRESH 45.0f // Degrees for sense ambiguity resolution
// Band table (11 entries: name, freq_MHz, element_length_m)
struct Band { const char* name; float freq; float el_len; };
const Band BANDS[] = {
{"160M", 1.850, 40.5},
{"80M", 3.650, 20.5},
// ... (all 11 bands)
};watson_watt.h — WatsonWatt Class
class WatsonWatt {
public:
// Sample both ADC channels and compute bearing
bool compute(float* adc_ns, float* adc_ew, float* adc_sense, int n);
// Returns bearing in degrees magnetic (0–360°)
float getBearingMag() const;
// Returns SNR in dB
float getSNR() const;
// Returns confidence level (0=INVALID, 1=LO, 2=MED, 3=HI)
int getConfidence() const;
// Calibration setters
void setNSGain(float g);
void setEWGain(float g);
void setOffset(float deg);
private:
float _rms_ns, _rms_ew; // RMS of each channel
float _bearing_raw; // atan2 result, degrees
float _bearing_resolved; // After sense ambiguity resolution
float _snr_db;
float _bearing_history[8];
int _history_idx;
float _ns_gain, _ew_gain;
float _offset_deg;
float computeRMS(float* samples, int n);
bool resolveSenseAmbiguity(float* sense_samples, int n, float raw_bearing);
float averageBearing(); // Circular mean of history buffer
};bearing.h — BearingDisplay Class
class BearingDisplay {
public:
void begin();
void update(); // Call from main loop (non-blocking)
// GPS access
float getLatitude() const;
float getLongitude() const;
bool hasGpsFix() const;
int getGpsFixType() const; // 0=none, 1=2D, 2=3D
// Compass access
float getCompassHeading() const; // Degrees magnetic
float getDeclination() const; // Degrees, + = East
void startCalibration();
void stopCalibration();
bool isCalibrating() const;
// Triangulation / waypoint
float bearingToWaypoint(float lat2, float lon2) const;
float distanceToWaypoint(float lat2, float lon2) const; // km
private:
// NMEA parser state
// QMC5883L reader
// Calibration min/max tracking
// Haversine computation
// IGRF declination lookup
};esp32_adf_hf.ino — Main Sketch Flow
setup():
init GPIO, UART, I2C, SPI
init OLED (display splash)
init GPS UART2
init QMC5883L
load NVS calibration data
start WiFi AP
start HTTP server
start Bluetooth SPP
start ADC timer (10 kHz, fills ring buffer)
motor_home() [if phased array mode]
display operational screen
loop():
if adc_buffer_ready:
ww.compute(ns_buf, ew_buf, sense_buf, BLOCK_SIZE)
update OLED
update BT output (if connected)
update web API JSON cache
bearingDisplay.update() // Non-blocking GPS + compass update
encoder.update() // Check for rotation or press
bt.processCommands() // Parse incoming BT command line
webServer.handleClient() // Handle HTTP requests
8.3 Doppler VHF Firmware (esp32_adf_vhf)
config.h (VHF Specific)
// Switch control
#define PIN_SW_A0 25 // FST3253 A0 select
#define PIN_SW_A1 26 // FST3253 A1 select
#define PIN_SW_OE 27 // FST3253 output enable (active low)
#define PIN_ADC_IN 34 // Audio input from receiver discriminator
// Doppler parameters
#define SWITCH_RATE_HZ 1000 // Switch frequency for 2M/70cm
#define SWITCH_RATE_HZ_UHF 2000 // For 33cm/20cm bands
#define ADC_RATE 10000 // Audio ADC sample rate
#define GOERTZEL_N 1024 // Block size
// VHF band table
struct VHFBand { const char* name; float freq_mhz; float el_len_mm; float diam_mm; };
const VHFBand VHFBANDS[] = {
{"2M", 146.0, 480.0, 1027.0},
{"1.25M", 222.0, 308.0, 676.0},
{"70cm", 435.0, 156.0, 330.0},
{"33cm", 915.0, 77.0, 163.0},
{"20cm", 1296.0, 55.0, 115.0}
};doppler.h — DopplerRDF Class
class DopplerRDF {
public:
void begin(int switch_rate_hz);
void processSamples(float* samples, int n); // Call with each ADC block
float getBearing() const; // Degrees magnetic, 0–360°
float getRSSI() const; // dBm (from AD8307 calibration curve)
float getSNR() const; // dB (phase coherence metric)
bool isValid() const;
void startCalibration(float known_bearing_true);
void addCalibrationPoint(float known_bearing_true);
void endCalibration();
void saveCalibration();
private:
// Goertzel algorithm state
float _s1, _s2;
float _coeff;
int _sample_count;
int _N;
// Phase reference (from switch ISR)
float _ref_phase_rad;
// Results
float _bearing_mag;
float _rssi_dbm;
float _snr;
bool _valid;
// Calibration
float _phase_offset_rad;
float _cal_sum;
int _cal_count;
float goertzelPhase(float* x, int n, float freq, float sample_rate);
float ad8307TodBm(int adc_counts);
};Timer ISR for Switching
The switch control is driven by a hardware timer ISR at the switch rate (1000 or 2000 Hz). Each ISR call advances the switch state machine:
ISR:
state = (state + 1) % 4
update switch GPIO outputs (A0, A1 pins)
update reference phase accumulator: ref_phase += 2π/4
set adc_trigger flag
The ADC is triggered by DMA at 10 kHz continuously. The Goertzel algorithm processes 1024-sample blocks corresponding to exactly 1024/10000 = 102.4 ms of audio, containing 102.4 cycles of the 1000 Hz subcarrier.
8.4 Rotary Loop Firmware (esp32_rotary_loop)
config.h (Loop Specific)
// Motor control (DRV8825)
#define PIN_STEP 25
#define PIN_DIR 26
#define PIN_ENABLE 27 // Active LOW
#define PIN_MS1 14 // Microstep select 1
#define PIN_MS2 12 // Microstep select 2
#define PIN_MS3 13 // Microstep select 3
// Position feedback
#define PIN_ENC_CS 5 // AS5048A SPI chip select
#define PIN_POT_ADC 35 // Potentiometer ADC (if encoder not present)
// Sense and tuning
#define PIN_SENSE_RELAY 32 // Sense antenna relay
#define PIN_VAR_DAC 25 // Varactor voltage (ESP32 DAC1)
#define PIN_CAP_RELAY_BASE 33 // First of 4 relay pins for cap bank
// Scan parameters
#define SCAN_STEP_DEG 2.0f // Coarse scan step
#define SCAN_SETTLE_MS 100 // Settle time per step
#define FINE_STEP_DEG 0.25f // Fine scan step (near null)
#define FINE_WINDOW_DEG 10.0f // Fine scan window around null candidate
// Gear ratio: motor to loop
#define GEAR_RATIO 3.0f
#define MOTOR_STEPS_REV 200
#define MICROSTEP_DIV 16
#define DEG_PER_MICROSTEP (360.0f / (MOTOR_STEPS_REV * MICROSTEP_DIV * GEAR_RATIO))loop_rdf.h — LoopRDF Class
class LoopRDF {
public:
void begin();
void update(); // Non-blocking, call from main loop every iteration
void home(); // Move to home position (blocking, max 5s)
// Scan control
void startScan();
void stopScan();
bool isScanComplete() const;
// Tuning
void autoTune(); // Sweep varactor, find peak RSSI (blocking ~8s)
// Results
float getNullBearing() const; // Degrees magnetic
float getNullDepth() const; // dB
float getConfidence() const; // 0–1
float getResolvedBearing() const; // After sense resolution, degrees mag
// Sense
void setSenseEnabled(bool en);
void resolveAmbiguity(); // Compare with/without sense, set resolved bearing
// Motor
void moveTo(float degrees); // Non-blocking move
float getCurrentPosition() const;
private:
// Motor state machine
enum MotorState { IDLE, MOVING, SCANNING, HOMING };
MotorState _motorState;
float _targetPos, _currentPos;
long _stepTarget, _stepCurrent;
unsigned long _lastStepTime;
int _stepInterval_us;
// Scan data
float _scanRSSI[181]; // RSSI at each 2° step (360°/2° = 180 points)
int _scanIdx;
// Null finding
float findNullGaussian();
float findNullMinimum();
// ADC
float readRSSI(); // Returns dBm from AD8307
float _varVoltage; // Current varactor DAC voltage
};Non-Blocking Stepper Implementation
The motor update function is called every pass of the main loop. It uses microsecond timing to generate steps without delaying the loop:
void LoopRDF::update() {
if (_motorState == IDLE) return;
unsigned long now = micros();
if (now - _lastStepTime >= _stepInterval_us) {
if (_stepCurrent != _stepTarget) {
digitalWrite(PIN_DIR, (_stepTarget > _stepCurrent) ? HIGH : LOW);
digitalWrite(PIN_STEP, HIGH);
delayMicroseconds(2);
digitalWrite(PIN_STEP, LOW);
_stepCurrent += (_stepTarget > _stepCurrent) ? 1 : -1;
_currentPos = _stepCurrent * DEG_PER_MICROSTEP;
_lastStepTime = now;
} else {
_motorState = IDLE;
// If scanning: record RSSI, advance scan index
if (/* scan mode */) {
_scanRSSI[_scanIdx++] = readRSSI();
// Advance to next scan position or finish
}
}
}
}8.5 CYD Display (cyd_adf_display)
The CYD display firmware is a single .ino file that:
- Initializes TFT_eSPI (ILI9341 320×240, SPI)
- Scans for available ADF Bluetooth devices (SerialBT.begin as master)
- Connects to the first discovered “RF-ADF” device
- Displays the MODE_COMPASS screen by default
- Processes BT data packets: JSON parsing for bearing, SNR, GPS, confidence
- Updates compass rose needle angle using TFT rotation transform
- Handles touch events (XPT2046 touch controller, I2C) for tab bar navigation
- Sends band commands back to ADF device when BAND mode tab is active
The compass rose is drawn once as a static background sprite, then the needle is redrawn each update by restoring the needle area from the background sprite and painting the new needle.
8.6 Bluetooth SPP Command Reference
Watson-Watt HF Commands
| Command | Syntax | Response | Description |
|---|---|---|---|
| BAND | BAND n | OK BAND n | Set band (0–10 per table) |
| STATUS | STATUS | JSON object | Report bearing, SNR, GPS, compass |
| CAL START | CAL START | OK CAL | Begin compass calibration rotation |
| CAL END | CAL END | OK SAVED / ERR | End calibration, save offsets |
| DECL | DECL f | OK DECL f | Override declination (degrees, + East) |
| OFFSET | OFFSET f | OK OFFSET f | Add fixed bearing offset (degrees) |
| GAIN NS | GAIN NS f | OK GAIN_NS f | Set N-S channel gain multiplier |
| GAIN EW | GAIN EW f | OK GAIN_EW f | Set E-W channel gain multiplier |
| RESET | RESET | (reboots) | Software reset |
| HELP | HELP | (lists commands) | Print command list |
Doppler VHF Commands
| Command | Syntax | Response | Description |
|---|---|---|---|
| BAND | BAND n | OK BAND n | Set band (0–4, see VHF band table) |
| STATUS | STATUS | JSON object | Report bearing, RSSI, GPS, compass |
| CAL START | CAL START f | OK CAL | Begin calibration, f = known bearing (true) |
| CAL ADD | CAL ADD f | OK CAL_PT | Add calibration point at known bearing f |
| CAL END | CAL END | OK SAVED | End calibration, compute and save offset |
| SQUELCH | SQUELCH n | OK SQL n | Set RSSI squelch level in dBm |
| DECL | DECL f | OK DECL f | Override declination |
| HELP | HELP | (lists commands) | Print command list |
Rotary Loop Commands
| Command | Syntax | Response | Description |
|---|---|---|---|
| SCAN | SCAN | OK SCANNING | Start full 360° coarse scan |
| STOP | STOP | OK STOPPED | Halt motor immediately |
| HOME | HOME | OK HOMING | Move to 0° reference position |
| MOVE | MOVE deg | OK MOVING | Move to absolute bearing position |
| BAND | BAND n | OK BAND n | Set band, auto-select cap bank |
| SENSE ON | SENSE ON | OK SENSE_ON | Enable sense antenna relay |
| SENSE OFF | SENSE OFF | OK SENSE_OFF | Disable sense antenna relay |
| TUNE | TUNE | OK TUNING | Start varactor auto-tune |
| STATUS | STATUS | JSON object | Report position, null, depth, GPS |
| HELP | HELP | (lists commands) | Print command list |
8.7 WiFi HTTP API
Common Endpoints (All Systems)
| Endpoint | Method | Request | Response | Notes |
|---|---|---|---|---|
| / | GET | none | HTML (full page) | Main web UI |
| /api/status | GET | none | JSON | Full status object |
| /api/bearing | GET | none | JSON bearing subset | Optimized for high-rate polling |
| /api/band | POST | {“band”:n} | {“ok”:true} | Set active band |
| /api/cal | POST | {“action”:“start”} or {“action”:“end”} | {“ok”:true} | Compass calibration |
| /api/decl | POST | {“decl”:f} | {“ok”:true} | Override declination |
| /api/reset | POST | none | (none, reboots) | Software reset |
Loop-Specific Endpoints
| Endpoint | Method | Request | Response | Notes |
|---|---|---|---|---|
| /api/scan | POST | {“action”:“scan”/“stop”/“home”} | {“ok”:true} | Motor control |
| /api/tune | POST | none | {“ok”:true} | Start auto-tune |
| /api/move | POST | {“bearing”:f} | {“ok”:true} | Move to position |
| /api/sense | POST | {“enable”:true/false} | {“ok”:true} | Sense relay control |
9.1 Watson-Watt Troubleshooting Matrix
| Symptom | Probable Cause(s) | Corrective Action |
|---|---|---|
| Bearing always shows 0° or 360° | N-S channel missing or grounded; relay stuck N-S position; NS coax shorted | Check NS relay with DMM; verify NS coax continuity; check INA128 NS input |
| Bearing always shows 90° or 270° | E-W channel missing or grounded; EW relay stuck; EW INA128 failed | Check EW relay and coax; swap INA128 and test |
| Bearing rotates continuously with stationary transmitter | Phase-matched feed lines not connected; INA128 oscillating; ADC input overdriven | Check feed line connections; reduce INA128 gain (increase Rg); add 10pF input cap to INA128 |
| SNR always shows 0 or < 1 dB | Signal not present; ADC input disconnected; INA128 unpowered | Verify signal present with handheld receiver; check 3.3V to INA128; check ADC input pin |
| Bearing off by exactly 90° | N-S and E-W coaxes swapped at junction box | Swap NS and EW coax labels at junction box |
| Bearing off by exactly 180° | Sense antenna polarity reversed; sense relay coil polarity wrong | Reverse sense coax center/shield; check relay wiring |
| Large scatter in repeated bearings (>15° std dev) | High site multipath; low SNR; element imbalance; intermittent connection | Increase SNR threshold; average more readings; check element connections; change site |
| Bearing off by constant angle (not 0°/90°/180°) | Array orientation offset; calibration offset incorrect | Re-measure array orientation; re-run OFFSET calibration |
| No GPS fix after 10 minutes | GPS antenna obstructed; GPS antenna cable fault; GPS module not powered | Move antenna to open sky; check 3.3V bias at GPS SMA; check UART2 output with serial monitor |
| Compass shows wrong heading | Hard-iron calibration not performed; strong magnetic interference nearby; compass mounted wrong axis | Re-run CAL START/END; move away from metal objects and motors; verify X-axis pointing forward |
| WiFi AP not visible | ESP32 in error state; WiFi init failed; antenna fault | Observe OLED — should show “WiFi OK”; if not, check serial debug output; power cycle |
| BT LED never lights | BT device not discovered; BT name mismatch; SPP not initialized | Scan for devices on phone; verify name matches config.h; check bt_handler.h init |
| One antenna pair giving wrong bearing (±45° error) | One element of the pair has wrong length, loose connection, or reversed polarity | Test each element individually with short vertical receive antenna as reference; check coax polarity |
| Bearing OK for some bands, wrong for others | Feed line phase mismatch is frequency-dependent; element length wrong for some bands | Re-verify phase matching at affected band frequencies with NanoVNA |
| OLED blank after power-up | OLED not initialized; I2C address wrong (0x3C vs 0x3D); 3.3V absent | Check I2C address jumper on OLED module; verify 3.3V; check SDA/SCL wiring |
| System reboots repeatedly (watchdog) | Task overrun; ADC DMA overflow; heap exhaustion | Enable serial debug; reduce ADC block size; check for heap leaks in BT handler |
| Bearing flips 180° occasionally | Sense signal marginal; sense relay bounce; sense coax too long | Increase sense threshold in config.h; add 100ns relay filter in firmware; shorten sense coax |
9.2 Doppler Troubleshooting Matrix
| Symptom | Probable Cause(s) | Corrective Action |
|---|---|---|
| No 1kHz subcarrier detected (Goertzel output near zero) | Receiver not connected to ADC input; switch IC not switching; audio level too low | Verify 3.5mm audio jack connection; check switch control GPIO outputs with LED; increase receiver volume |
| Bearing always same regardless of signal direction | Switch IC failed (all elements tied together); phase offset near 0° | Test switch by measuring element RSSI individually; check FST3253 supply |
| Bearing correct for some directions, wrong for others | One element has wrong length or bad connection | Test each element with short vertical as reference; swap elements |
| Switch IC not switching (all GPIO correct) | FST3253 Vcc not connected; OE line stuck HIGH; ESD damage | Check FST3253 pin 16 for 3.3V; verify OE is driven LOW; replace IC |
| High phase noise (bearing jitter > 10°) | Low SNR signal; receiver audio clipping or distortion; ADC sample rate mismatch | Reduce audio input level; move closer to transmitter; verify ADC sample rate in timer |
| Bearing always 0° or stuck | Phase offset from calibration wrong; CAL data corrupted | Issue CAL END without preceding CAL START to clear;
re-run calibration |
| Bearing 180° wrong | Element rotation direction reversed (S→W→N→E instead of N→E→S→W) | Swap A0/A1 pins in config.h, or swap element 1 and element 3 connectors |
| No bearing on 33cm or 20cm band | PE4259 switch not installed; frequency too high for FST3253 | Verify PE4259 is installed for 33cm/20cm bands; check switch configuration jumper |
| Bearing drifts slowly over time | Reference oscillator drift; receiver frequency drift; temperature effect | Verify ESP32 crystal clock stability; use temperature-compensated receiver |
| Array doesn’t level on tripod | Tripod pan head not level; disk warp (PLA in heat) | Adjust tripod legs; replace with PETG print |
9.3 Rotary Loop Troubleshooting Matrix
| Symptom | Probable Cause(s) | Corrective Action |
|---|---|---|
| Motor doesn’t move on SCAN command | ENABLE pin stuck HIGH; DRV8825 fault; motor not connected | Check PIN_ENABLE GPIO output (should go LOW); check DRV8825 FAULT LED (if present); check motor 4-wire cable |
| Motor moves but no position change (AS5048A) | Magnet missing or misaligned; SPI wiring error; I2C address conflict | Verify magnet is centered on shaft; check SPI CS, MOSI, MISO, SCK; use STATUS command to read raw encoder value |
| Motor steps but position reads wrong | Gear ratio constant wrong in config.h; microstep setting mismatch | Verify GEAR_RATIO = 3.0; verify DRV8825 MS1/MS2/MS3 pins match config.h MICROSTEP_DIV |
| Null depth < 10 dB | Faraday shield gap closed; feed point asymmetry; loop asymmetry | Check shield gap (10mm, must be open); measure capacitance from each terminal to ground; remeasure loop dimensions |
| Scan completes but no null found | Signal too weak; band mismatch; varactor not tuned | Verify signal SNR > 15 dB; run TUNE before SCAN; select correct band |
| Loop won’t tune (TUNE command finds no peak) | Varactor reverse-biased; DAC1 not working; band capacitor relay failed | Check DAC1 output (GPIO25, should sweep 0–3.3V); check varactor polarity in schematic; check relay driver |
| Bearing off by exactly 90° | Loop shaft bracket mounted 90° wrong | Rotate shaft bracket 90° and re-tighten set screws |
| Motor oscillates back and forth during scan | Step rate too fast; supply voltage sag; DRV8825 Vref too low | Increase SCAN_STEP_INTERVAL_MS in config.h; check 12V under load; increase DRV8825 Vref to 0.75V |
| Scan position drifts over time (missed steps) | DRV8825 current too low; motor overheating | Increase DRV8825 Vref; allow motor to cool between scans |
| Sense resolution gives wrong result | Sense antenna directional (not omnidirectional); RC phase shift off-frequency | Use shorter sense wire (< λ/20); verify RC values match operating frequency |
9.4 Fault Codes Reference
| Fault Code | OLED Message | Cause | Action |
|---|---|---|---|
| E01 | “COMPASS FAIL” | QMC5883L not responding on I2C | Check I2C wiring, pull-ups; replace module |
| E02 | “GPS NO DATA” | GPS UART not receiving NMEA | Check GPS cable; verify 3.3V bias to GPS antenna |
| E03 | “ADC OVERFLOW” | ADC ring buffer overrun | Reduce ADC sample rate; optimize loop() |
| E04 | “CAL INVALID” | Calibration did not complete full rotation | Repeat calibration with full 2-turn rotation |
| E05 | “NS OPEN” | N-S channel input floating (no signal path) | Check NS coax and relay |
| E06 | “EW OPEN” | E-W channel input floating | Check EW coax and relay |
| E07 | “MOTOR FAULT” | DRV8825 FAULT line asserted | Check motor current, check for short in motor winding |
| E08 | “ENCODER ERR” | AS5048A not responding on SPI | Check SPI wiring; verify 3.3V to encoder |
| E09 | “WIFI FAIL” | WiFi AP initialization failed | Power cycle; check for antenna damage on ESP32 |
| E10 | “NVS CORRUPT” | Calibration data in NVS unreadable | Issue RESET; re-run all calibrations |
| E11 | “ADC CLIP” | ADC input voltage exceeds 3.3V | Add attenuator (voltage divider) at ADC input |
| E12 | “BT FAIL” | Bluetooth initialization failed | Power cycle; check for concurrent WiFi/BT resource conflict |
# CHAPTER 10 — PREVENTIVE MAINTENANCE
10.1 Maintenance Schedule
| Interval | Task | Reference |
|---|---|---|
| After each field use | Visual inspection of all cables and connectors | 10.2 |
| After each field use | Remove and dry any condensation from coax connectors | 10.2 |
| After each field use | Clean mud or debris from printed parts and element sockets | 10.4 |
| After each field use | Stow elements and coax in labeled bags to prevent loss | — |
| Monthly | Check element connection tightness (M3 set screws) | 10.2 |
| Monthly | Inspect coax for kinks, damage to jacket | 10.2 |
| Monthly | Verify LED indicators function correctly | 10.1 |
| Monthly | Confirm WiFi and BT initialize and connect normally | Chapter 5 |
| Quarterly | Re-run compass calibration | 5.2 |
| Quarterly | Verify GPS cold start fix in < 60 seconds | 5.1 |
| Quarterly | Check motor coupling set screws (loop system) | 10.3 |
| Quarterly | Check belt tension (loop system) | 10.3 |
| Quarterly | Verify DRV8825 Vref is 0.75V ± 0.05V (loop) | 10.3 |
| Annually | Inspect shaft bearing/bushing for wear (loop) | 10.3 |
| Annually | Check INA128 gain resistor soldering (HF system) | 10.4 |
| Annually | Clean loop copper surface (loop system) | 10.3 |
| Annually | Update GPS firmware if manufacturer release available | — |
| Annually | Full 4-point bearing calibration verification | 6.1, 6.2 |
10.2 Connector Maintenance
BNC Connectors
- Inspect BNC connectors on all element feed lines at the junction box and controller box inputs.
- Look for: green oxidation on center pin, cracked or deformed dielectric, bent center pin, cracked outer ring.
- Clean oxidized center pins with a cotton swab dampened with isopropyl alcohol (IPA, 90%+). Allow to dry before reconnecting.
- Verify center pin retention: the pin should not move when pushed with a fingernail. A loose center pin indicates the coax needs re-termination.
- Torque standard: finger-tight plus one-quarter turn with a 7/16” wrench. Do not over-tighten BNC; the bayonet lock prevents over-tightening, but over-forcing can crack the mating connector.
SMA Connectors (GPS, VHF array)
- Inspect SMA connectors for bent center pins (very common damage from improper mating).
- A bent SMA center pin (0.9mm diameter) is difficult to straighten without breaking. Replace the connector rather than attempting to straighten.
- Mate SMA connectors by first aligning by hand until the connector rotates freely, then tighten with a 5/16” torque wrench to 0.56 N·m (5 in-lb).
- Clean SMA connectors with IPA and a foam swab. Allow to dry completely before mating.
- Cap unused SMA connectors with SMA dust caps.
Coax Inspection
- Inspect the full length of all feed line coax once per month.
- Flex each coax through its full range of motion. Listen and feel for crunching (broken shield braid or kinked center conductor).
- If any coax shows physical damage (deep kinks, jacket cuts, exposed shield, delaminated foam dielectric), replace the entire run. Do not splice coax in a feed line run; any splice changes the electrical length.
- Verify strain relief at all connector terminations. The coax must not be under tension at the connector backshell; movement must be absorbed by the cable, not the connector.
10.3 Loop System Maintenance
Belt and Pulleys
- Inspect the GT2 belt for cracks, missing teeth, or fraying of the edge cords. Replace if any damage is present.
- Check belt tension: deflect the belt at mid-span with light finger pressure (approximately 2N). Correct deflection: 5mm. If deflection exceeds 10mm, the belt is too loose. If the belt cannot be deflected 2mm, it is too tight.
- Adjust belt tension by loosening the two motor mounting screws, sliding the motor to the desired position, and retightening. Recheck tension.
- Inspect pulley set screws quarterly. Tighten with M3 hex key. Apply one drop of thread-locker (medium strength, e.g., Loctite 243) to each set screw after final tightening.
Shaft and Bearings
- Check the loop shaft for lateral play in the bearing block. Acceptable play: < 0.5mm total lateral movement. Excess play causes loop wobble and degraded null depth.
- Apply one drop of light machine oil (ISO 32 or sewing machine oil) to the bearing block shaft surfaces annually.
- Do not use grease in the bearing block; grease accumulates dust and accelerates wear.
Motor Driver Vref
The DRV8825 Vref sets the peak motor current. Incorrect Vref causes missed steps (too low) or motor overheating (too high).
With the motor connected and powered, set a DMM to DC volts, reference probe to GND.
Measure the voltage on the DRV8825 Vref trimmer (small potentiometer on the DRV8825 module, accessible with controller box open).
Correct Vref for NEMA 17 at 1.5A setting:
Vref = I_max / 2 = 1.5 / 2 = 0.75VAdjust the trimmer until Vref = 0.75V ± 0.05V. Use a small flat-blade screwdriver; turn slowly and re-measure.
Copper Loop Cleaning
- Once per year, clean the copper tube loop surface to remove oxidation.
- Use 000-grade steel wool (fine grade). Rub gently along the length of each tube.
- Do not clean the Faraday shield copper foil with steel wool. Wipe the shield foil with IPA on a cloth.
- After cleaning, apply a light coat of lacquer spray to slow re-oxidation. Keep lacquer off the feed point terminals.
10.4 HF System Maintenance
INA128 Gain Resistor
The INA128 instrumentation amplifier gain is set by a single external resistor (Rg) between pins 1 and 8. Gain formula:
G = 1 + (49.4kΩ / Rg)
For Rg = 3Ω: G = 1 + (49400/3) = 16,467 ≈ 84 dB.
- Inspect the Rg solder joints annually. This resistor is small (0402 or 0603 package) and subject to solder joint cracking from thermal cycling.
- Use magnification (×10 loupe) to inspect the solder fillets. Cold or cracked joints appear dull or fractured.
- Reflow suspect joints with a fine-tipped soldering iron (320°C, clean tip, solder flux).
- If the gain is suspected low (weak bearing output), measure Rg resistance in circuit with the IC powered off: resistance should match the installed value within ±2%.
Phase-Matched Coax Re-Verification
After any coax replacement or repair:
- Re-verify electrical length match per Section 4.1 (Phase Matching Verification).
- If NanoVNA is not available, a useful field check is to connect all four element coax ends to the junction box and compare signal levels from a source at 45° to the array: V_NS and V_EW should be approximately equal (within ±3 dB) for a signal at 45°.
11.1 Common Electronics (All Systems)
| Item | Qty | Specification | Typical Source |
|---|---|---|---|
| ESP32-WROOM-32 module | 1 | 4MB flash, PCB trace antenna, 38-pin module | AliExpress, Mouser |
| QMC5883L magnetometer module | 1 | I2C, 3.3V, breakout board with 4.7kΩ pull-ups | AliExpress |
| u-blox NEO-8M GPS module | 1 | UART, 3.3V, with active patch antenna | AliExpress (HMC module) |
| SSD1306 OLED display | 1 | 128×64, I2C, 3.3V, 0.96” | AliExpress |
| MPU6050 IMU module | 1 | I2C, 3.3V, gyro + accel, for tilt correction | AliExpress |
| AD8307 logarithmic detector | 1 | SOIC-8, 500 MHz, 92 dB dynamic range | Mouser, DigiKey |
| AMS1117-3.3 LDO regulator | 1 | SOT-223, 800 mA | DigiKey |
| 78L05 voltage regulator | 1 | TO-92, 100 mA, 5V | DigiKey |
| 2.1mm barrel jack (PCB) | 1 | Center positive, 2A rating | DigiKey |
| Green LED 3mm | 1 | 520 nm, 20 mA | DigiKey |
| Blue LED 3mm | 1 | 470 nm, 20 mA | DigiKey |
| Rotary encoder w/ switch | 1 | 20 PPR, 6mm D-shaft, momentary push | AliExpress |
| PCB enclosure | 1 | 115×65×30 mm, ABS, with panel cutout | AliExpress |
| Misc passives (R, C) | 1 set | 0402/0603 SMD, values per schematic | DigiKey |
11.2 Watson-Watt HF Specific Components
| Item | Qty | Specification | Notes |
|---|---|---|---|
| Aluminum element rods | 4× sets | 6mm OD, various lengths per band (see Section 3.1 table) | Cut to length; hardware store or online metals |
| RG-58C/U coaxial cable | 1 roll | 50Ω, solid PE dielectric, copper braid, foam jacket | 25m minimum for 40M array setup |
| PL-259 (UHF male) connectors | 10 | For RG-58 termination | DigiKey |
| SO-239 (UHF female) panel connectors | 6 | For junction box | DigiKey |
| FT-50-43 toroid core | 4 | 12mm OD, Fair-Rite type 43 material | Mouser, Amidon |
| Magnet wire | 1 spool | 24 AWG, enamel coated, for balun winding | Electronics distributors |
| INA128 instrumentation amplifier | 2 | DIP-8 or SOIC-8, Rg = 3Ω for 84dB gain | Mouser, DigiKey |
| DPDT relay, 12V coil | 2 | 10A contact rating, PCB mount | DigiKey |
| GALI-84 MMIC LNA | 2 | DC–6 GHz, 13 dB gain, 50Ω | Mini-Circuits |
| BAT46 Schottky diodes | 8 | 100V, 150 mA, for relay flyback protection | DigiKey |
| Element base stakes | 4 | Printed PETG, stake form | Print from STL file |
| Ground stake (center) | 1 | 30cm steel or fiberglass, 19mm OD | Garden supply |
| Junction box enclosure | 1 | NEMA-1, 150×100×75mm, ABS | Electrical supply |
11.3 Doppler VHF Specific Components
| Item | Qty | Specification | Notes |
|---|---|---|---|
| FST3253 dual 1:4 analog multiplexer | 1 | TSSOP-16, 3.3V, ≤400 MHz | DigiKey, Mouser |
| PE4259 SPDT RF switch | 2 | DC–3 GHz, SOT-26, for 33cm/20cm bands | pSemi/Mouser |
| BNC female panel connector | 4 | 50Ω, PCB or panel mount | DigiKey |
| 5mm aluminum rod | 1 set | Various lengths per band (see Section 2.2) | Hardware store |
| Doppler ring disk | 1 per band | PETG print, 3mm, band-specific diameter | Print from STL file |
| 1/4”-20 brass insert | 1 | Heat-set, for tripod mount | McMaster-Carr |
| RG-316 coaxial cable | 2m | 50Ω, 2.5mm OD, SMA connectors | Mouser |
| SMA female PCB connector | 5 | Edge-launch or right-angle | DigiKey |
| Camera tripod | 1 | Standard 1/4”-20 head, extend to 2m | Camera supply |
11.4 Rotary Loop Specific Components
| Item | Qty | Specification | Notes |
|---|---|---|---|
| 4mm copper tube | 2m | 4mm OD, refrigeration/plumbing grade | Plumbing supply |
| 90° copper elbow fittings | 4 | 4mm, sweat fit | Plumbing supply |
| Electrical solder, lead-free | 1 tube | 60/40 or 63/37 Sn/Pb (or SAC305) | Electronics supply |
| Copper foil tape (Faraday shield) | 1 roll | 25mm wide, conductive adhesive | DigiKey |
| GT2 timing belt | 1 | 300mm length, 6mm wide, 2mm pitch | Robotics supply |
| GT2 pulley, 20 tooth | 1 | 5mm bore, for motor shaft | Robotics supply |
| GT2 pulley, 60 tooth | 1 | 8mm bore, for loop shaft | Robotics supply |
| NEMA 17 stepper motor | 1 | 1.5A, 12V, 200 steps/rev, 40 N·cm holding | Robotics supply |
| DRV8825 driver module | 1 | With trim pot, heat sink | AliExpress |
| J310 JFET | 2 | N-channel, TO-92, for buffer amplifier | DigiKey |
| AS5048A magnetic encoder | 1 | SPI, 14-bit, absolute, for loop position | DigiKey |
| Diametrically magnetized magnet | 1 | 6mm diameter, for AS5048A | Supermagnete |
| Varactor diode BB112 | 2 | 2–10 pF, 30V, SOD-323 | DigiKey |
| Small signal relay, DPDT | 4 | 5V coil, for capacitor bank switching | DigiKey |
| Fixed capacitors (SMD) | 1 set | Values per band (see Section 1.5 table) | DigiKey |
| 8mm shaft, 150mm | 1 | Aluminum, straight, for loop shaft | Metals supply |
| Flanged bearing, 8mm | 2 | For loop shaft bearing block | Robotics supply |
| GT2 belt idler, 3mm bore | 1 | For belt tensioning (optional) | Robotics supply |
11.5 CYD Display Components
| Item | Qty | Specification | Notes |
|---|---|---|---|
| ESP32-2432S028R (CYD) | 1 | 320×240 TFT, ILI9341, XPT2046 touch, ESP32 | AliExpress (“Cheap Yellow Display”) |
| USB-C cable | 1 | For programming and power | Standard |
| CYD enclosure (optional) | 1 | Printed PLA/PETG, with display cutout | Print from STL file |
| Battery + TP4056 (optional) | 1 set | 18650 + 3.7V charger + 5V boost | For portable CYD operation |
# APPENDIX A — BAND-BY-BAND OPERATING NOTES
160 Meters (1.8–2.0 MHz)
The 160-meter band presents the most challenging array deployment of any HF band. A quarter-wave element length of 40.5 meters requires a very large field site. The Watson-Watt baseline of 40.5 meters will rarely be achievable in portable field work; shortened elements (loaded or folded) can reduce the physical size at a cost of 10–20 dB in sensitivity. Ground conductivity strongly affects the elevation angle of arriving signals, and low-angle groundwave propagation at 160M means that the incoming angle is nearly horizontal — ideal for the vertical element. The Adcock array is the recommended technique; the rotary loop is an excellent alternative because its small physical size makes up for the element length limitation of the Adcock. Site effects (nearby buildings, buried conductors, soil moisture variation) have more impact at 160M than any other HF band. The band is used extensively for AM broadcast and amateur medium-wave operation; the primary RDF use case is broadcast station identification and NVIS propagation research.
80 Meters (3.5–4.0 MHz)
The 80M band is a workhorse HF RDF band. The λ/4 element length of 20.5 meters is achievable at a large park or open field. Skywave propagation begins at distances of about 300 km at night, with groundwave reliable to 400–600 km on typical soil. The Watson-Watt system performs well on 80M AM and SSB signals. Bearing accuracy on NVIS signals (arriving from near-vertical angles) is degraded relative to groundwave signals; use the SNR and confidence indicators to assess bearing quality. The rotary loop is effective on 80M with the 400mm loop, achieving null depths of 22–28 dB. Noise environment: 80M is moderately noisy (power line harmonics, plasma displays, switching supplies); select a site well away from power lines. Primary use case: amateur NVIS beacon tracking, broadcast station identification, emergency communications RDF support.
60 Meters (5.3–5.4 MHz)
The 60M band (five channelized frequencies in the US) is used primarily for NVIS emergency communications. Element length 14.1 meters. The Watson-Watt array is well-suited for 60M. Propagation is NVIS during daytime and transitions to skywave at night. The band has relatively low interference compared to 40M and 80M. Primary use case: emergency communications station identification and direction finding.
40 Meters (7.0–7.3 MHz)
The 40M band is the optimal band for HF RDF operations. The 10.5m element length is easily managed with telescoping aluminum rods. Watson-Watt accuracy is typically best on 40M due to the balanced element size and moderate frequency (less ground effect dispersion than 80M, less multipath than 20M). Interference environment: 40M is heavily used by broadcast stations, amateurs, and utilities worldwide. The ADF system should be used during relatively quiet periods or with a narrow-band bandpass filter in the receive chain to isolate the target signal. The Doppler ring technique is not applicable below VHF; Watson-Watt or rotary loop are the choices for 40M. Primary use cases: amateur NVIS, utility station identification, emergency preparedness.
30 Meters (10.1–10.15 MHz)
The 30M band is narrow (50 kHz), exclusively CW and data modes (no phone). Element length 7.4 meters is compact and portable. The Watson-Watt and rotary loop systems both work well on 30M. The rotary loop is preferred for CW signals, as its narrow bandwidth helps reject adjacent interference. The band has moderate propagation distance for RDF use. Primary use case: CW beacon tracking, digital mode station identification.
20 Meters (14.0–14.35 MHz)
The 20M band is the primary DX HF band. Element length 5.3 meters is very portable. Watson-Watt works well, but incoming signals from DX paths arrive at low elevation angles that may increase multipath from nearby terrain features. The 20M band is active 24 hours. Short skip (ground wave to 200km) and long skip (> 4000km) coexist; the ADF system cannot distinguish between them. Bearing accuracy on high-angle NVIS signals is reduced; best accuracy is achieved on low-angle groundwave signals below 200km distance. Primary use: DX station direction, propagation research, beacon tracking.
17 Meters (18.068–18.168 MHz)
The 17M band (WARC, non-contest) is quieter than 15/20M. Element length 4.1 meters. Watson-Watt performance is good. The band supports moderate DX; good for 1000–10,000 km path RDF on SSB signals. Primary use: propagation research.
15 Meters (21.0–21.45 MHz)
Element length 3.5 meters. Watson-Watt is compact and easily deployed. 15M supports long-distance DX when the solar cycle is favorable. The rotary loop is the highest band at which the 400mm loop is usefully sensitive (requires careful varactor tuning). Primary use: DX propagation research, amateur SSB/CW station identification.
12 Meters (24.89–24.99 MHz)
Element length 3.0 meters. Watson-Watt works well with reduced baseline. The 400mm rotary loop is marginal at 12M (sensitivity reduced by loop being electrically smaller relative to wavelength). Watson-Watt preferred. Primary use: propagation research, beacon tracking.
10 Meters (28.0–29.7 MHz)
Element length 2.6 meters. Watson-Watt is compact (2.6m elements). 10M opens during solar maximum to worldwide DX and closes completely during solar minimum. A very useful band for RDF when conditions are good. Primary use: DX beacon tracking, sporadic-E propagation research.
6 Meters (50–54 MHz)
Element length 1.5 meters. Watson-Watt array is small and easily portable. 6M exhibits sporadic-E and meteor scatter propagation; bearings may be unstable on propagated signals. On local groundwave signals (< 100km), Watson-Watt achieves excellent accuracy. Doppler ring is possible at 6M with a printed disk of 950mm diameter. Primary use: sporadic-E research, local emergency communications RDF, foxhunting.
2 Meters (144–148 MHz)
The 2M band is the primary VHF RDF band. The Doppler ring with 1027mm diameter array is the standard technique. Array fits on a standard camera tripod and can be deployed in 2 minutes. Bearing accuracy ±1°–3° on typical FM signals. Watson-Watt is also feasible with 480mm elements (compact). Primary use cases: foxhunting, APRS vehicle tracking, emergency ARES/RACES operations, repeater identification.
1.25 Meters (222–225 MHz)
The 1.25M band (North American allocation) uses a 676mm Doppler disk. The band is lightly used relative to 2M; primary users are amateur repeaters and some digital modes. The Doppler system works identically to 2M but with a smaller, lighter array disk. Primary use: foxhunting on 222 MHz band.
70 Centimeters (420–450 MHz)
The 70cm band uses a 330mm Doppler disk — compact enough to be handheld with a small mast. The band includes amateur FM, repeaters, satellite downlinks, and some digital modes. For the FST3253 switch (rated to ~400 MHz), performance at 450 MHz may be degraded; use the PE4259 at higher 70cm band segments. Primary use: foxhunting, satellite beacon tracking, ATV direction finding.
33 Centimeters (900–928 MHz)
The 33cm band includes ISM devices (915 MHz), amateur repeaters, and data systems. The 163mm Doppler disk is small and lightweight. The PE4259 switch is required at this frequency. Array element length is 77mm — short enough to print on the disk itself. Primary use: ISM transmitter locating, amateur 900 MHz operations.
20 Centimeters (1240–1300 MHz)
The 20cm band (23cm band in Region 1) is the highest band in this ADF suite. The 115mm Doppler disk is very small (fits in a shirt pocket) and is mounted directly on the receiver or on a small tripod adapter. The PE4259 switch is required; the ESP32 GPIO switching rate must be 2000 Hz for this band for adequate modulation depth. Element length 55mm is printed as stubs on the disk. Primary use: amateur microwave foxhunting, GPS L1/L2 antenna research (note: GPS is receive-only; GPS receivers cannot be direction-found in the traditional sense — this band is for amateur microwave transmitters).
# APPENDIX B — WATSON-WATT ALGORITHM DERIVATION
B.1 Signal Model
Consider a linearly polarized plane wave arriving from azimuthal bearing θ (measured clockwise from True North) and elevation angle α above the horizontal. The electric field component at a point in space is:
E(t, r) = E₀ × cos(2πft - k·r + φ₀)
Where: - E₀ = field amplitude (V/m) - f = signal frequency (Hz) - k = wave vector (magnitude 2π/λ, direction of propagation) - r = position vector - φ₀ = initial phase
B.2 Adcock Element Signals
For the North element located at position (0, +d/2, 0) (d = array baseline):
V_N(t) = h_eff × E₀ × cos(2πft - k_y × d/2 + φ₀)
Where h_eff is the effective height of the monopole element, and k_y is the northward component of the wave vector:
k_y = (2π/λ) × sin(θ) × cos(α)
For the South element at (0, -d/2, 0):
V_S(t) = h_eff × E₀ × cos(2πft + k_y × d/2 + φ₀)
B.3 Differential Output (N-S Pair)
The differential output of the N-S pair is:
V_NS = V_N - V_S
= h_eff × E₀ × [cos(2πft - k_y×d/2 + φ₀) - cos(2πft + k_y×d/2 + φ₀)]
Using the identity cos(A - B) - cos(A + B) = 2×sin(A)×sin(B), with A = 2πft + φ₀ and B = k_y×d/2:
V_NS = 2 × h_eff × E₀ × sin(2πft + φ₀) × sin(k_y × d/2)
For small arrays (k_y × d/2 << 1 radian, i.e., d << λ), sin(k_y × d/2) ≈ k_y × d/2:
V_NS ≈ 2 × h_eff × E₀ × sin(2πft + φ₀) × (k_y × d/2)
= h_eff × E₀ × d × k_y × sin(2πft + φ₀)
= h_eff × E₀ × d × (2π/λ) × sin(θ) × cos(α) × sin(2πft + φ₀)
The phase-shifted form (using cos rather than sin) is achieved by a 90° phase reference, but for RMS computation the phase is irrelevant:
V_NS(t) = A × sin(θ) × cos(α) × cos(2πft + φ)
Where A = h_eff × E₀ × d × (2π/λ) × [constant amplitude factor].
Similarly for the E-W pair:
V_EW(t) = A × cos(θ) × cos(α) × cos(2πft + φ)
Both signals are at the same frequency, in phase with each other (same phase φ), with amplitudes proportional to sin(θ) and cos(θ) respectively.
B.4 RMS Computation
The RMS value of V_NS over one or more complete cycles of the carrier:
V_NS_rms = (A/√2) × |sin(θ)| × cos(α)
V_EW_rms = (A/√2) × |cos(θ)| × cos(α)
The term cos(α) (elevation angle factor) affects both channels equally and cancels in the ratio. The amplitude factor A also cancels.
B.5 The Watson-Watt Bearing Formula
The bearing is extracted using:
θ = atan2(V_NS_rms, V_EW_rms)
The atan2 function, defined as atan2(y, x) = arctan(y/x) with correct quadrant selection based on the signs of x and y, gives the bearing directly:
θ = atan2(sin(θ), cos(θ)) = θ (by definition of atan2)
In practice, V_NS_rms and V_EW_rms are computed by the firmware as:
V_NS_rms = sqrt( (1/N) × sum_{n=0}^{N-1} v_NS[n]^2 )
V_EW_rms = sqrt( (1/N) × sum_{n=0}^{N-1} v_EW[n]^2 )
Note: The RMS computation loses sign information. The atan2 result is always in the first quadrant (0°–90°) without sign-preserving information. To recover the full 0°–360° range, the firmware uses the sample-by-sample correlation between the N-S and E-W waveforms, which carries sign information:
Sign_NS = sign of mean(v_NS × reference_cos) // Cross-correlate with cosine reference
Sign_EW = sign of mean(v_EW × reference_cos)
θ_full = atan2(Sign_NS × V_NS_rms, Sign_EW × V_EW_rms)
Where reference_cos is a synthetic cosine generated by the firmware at the estimated signal frequency. In practice, the sense antenna provides the reference.
B.6 Ambiguity Discussion
The atan2 function maps (sin θ, cos θ) → θ correctly for all θ in -180° to +180°. However, the magnitudes |sin θ| and |cos θ| (which is what the RMS values measure, because squaring loses the sign) map the same value for θ and θ+180°:
|sin(θ)| = |sin(θ + 180°)|
|cos(θ)| = |cos(θ + 180°)|
Therefore, without the sense antenna, the bearing is ambiguous by 180°: the atan2(|sin θ|, |cos θ|) result is indistinguishable between θ and θ+180°.
Sense antenna resolution: The sense antenna signal V_SENSE has no directional dependence (omnidirectional). When combined with the loop output (either N-S or E-W):
V_combined = V_loop + jV_sense
This produces a cardioid pattern: V_combined is maximum when the loop and sense signals are in phase, and V_combined = 0 when they cancel. The cardioid has only one null and one maximum.
The firmware compares the phase of V_SENSE with V_NS: if in phase (cos of phase difference > 0), the transmitter is in the positive sin(θ) half-space (bearing 0°–180° from the N direction). If out of phase, the transmitter is in the negative sin(θ) half-space (bearing 180°–360°). The 180° ambiguity is thereby resolved.
# APPENDIX C — DOPPLER EQUATION DERIVATION
C.1 Classical Doppler Shift
A receiver moving with velocity v toward a stationary transmitter at frequency f₀ observes a shifted frequency:
f_received = f₀ × (1 + v/c) (approaching)
f_received = f₀ × (1 - v/c) (receding)
More precisely, for a receiver moving at angle β relative to the direction toward the transmitter:
f_received = f₀ × (1 + (v/c) × cos(β))
The instantaneous frequency shift is:
Δf(t) = f₀ × (v(t)/c) × cos(β(t))
C.2 Rotating Receiver
If the receiver antenna moves in a circle of radius r at angular velocity ω (rad/s), the tangential velocity is v = r × ω, and the angle β between the velocity vector and the direction to the transmitter varies as:
β(t) = θ - φ(t) - 90°
Where θ = bearing to transmitter, φ(t) = ωt = instantaneous angular position of the antenna.
The term cos(β) becomes:
cos(θ - φ(t) - 90°) = sin(θ - φ(t)) = sin(θ)cos(φ(t)) - cos(θ)sin(φ(t))
The Doppler frequency shift is:
Δf(t) = (f₀ × v / c) × sin(θ - φ(t))
= (f₀ × r × ω / c) × sin(θ - ωt)
This is a sinusoidal frequency modulation at the rotation rate ω (in Hz: f_switch = ω/(2π)), with phase equal to the bearing θ. The FM index (maximum frequency deviation) is:
β_FM = f₀ × r × ω / (c × f_switch × 2π)
= f₀ × r / (c / (2π) × ...)
Simplified: For the 2M band at 146 MHz, array radius r = 0.5m, ω = 2π × 1000 rad/s:
Δf_max = (146×10⁶ × 0.5 × 2π×1000) / (3×10⁸)
= (146×10⁶ × 0.5 × 6283) / (3×10⁸)
= 458×10⁶ / (3×10⁸)
= 1.53 Hz (peak Doppler shift)
This 1.53 Hz peak deviation, at a 1000 Hz modulation rate, produces a 1 kHz FM subcarrier with an FM index of:
m_FM = Δf_max / f_switch = 1.53 / 1000 = 0.00153 radians
This is a very small FM index; the sidebands are very weak. This is why the Goertzel algorithm must extract the phase of the subcarrier with high precision despite its very low modulation depth.
C.3 Electronic Switching Equivalence
The electronic switching system does not continuously rotate a single antenna; instead, it discretely samples each of four fixed antennas in sequence. This discrete sampling creates a phase-stepped approximation of the continuously rotating receiver.
For a 4-element array switched at rate f_s = 1000 Hz, each element is active for T = 1/(4 × f_s) = 250 µs. The received signal from element i at angular position φ_i = i × 90° (for i = 0, 1, 2, 3) during its active interval is:
x_i(t) = E₀ × cos(2π f₀ t + k × r × cos(θ - φ_i) + φ_0)
The phase of the signal from element i is:
φ_signal_i = k × r × cos(θ - φ_i)
As i advances from 0 to 3 (N → E → S → W), the phase of the sampled signal steps as cos(θ - 0°), cos(θ - 90°), cos(θ - 180°), cos(θ - 270°), which is sin(θ), sin(θ - 90°), sin(θ - 180°), sin(θ - 270°). This is a sinusoidal phase sequence at the switching frequency, and its phase offset is exactly θ, the bearing to the transmitter.
The FM discriminator output of the receiver converts this phase modulation to an audio tone at frequency f_switch = 1000 Hz, with phase = θ.
C.4 Goertzel Algorithm Equations
The Goertzel algorithm computes the DFT at a single frequency bin k₀ corresponding to the target frequency f_target = f_switch = 1000 Hz.
Parameters:
N = 1024 (block size, samples)
f_s = 10000 (sample rate, Hz)
f_target = 1000 Hz
k₀ = N × f_target / f_s = 1024 × 1000 / 10000 = 102.4
Since k₀ must be an integer for the standard Goertzel algorithm, choose N and f_s such that k₀ is exactly an integer. With N = 1000, f_s = 10000, k₀ = 100 exactly.
Coefficient:
ω₀ = 2π × k₀ / N = 2π × 100 / 1000 = 0.6283 rad/sample
coeff = 2 × cos(ω₀) = 2 × cos(0.6283) = 2 × 0.8090 = 1.6180
Recursive loop (for samples x[0] through x[N-1]):
Initialize: s_prev = 0, s_prev2 = 0
For n = 0 to N-1:
s = x[n] + coeff × s_prev - s_prev2
s_prev2 = s_prev
s_prev = s
Result extraction:
X_re = s_prev - s_prev2 × cos(ω₀)
X_im = s_prev2 × sin(ω₀)
Magnitude: |X| = sqrt(X_re² + X_im²)
Phase: φ = atan2(X_im, X_re) [radians]
Bearing extraction:
θ_measured = φ - φ_reference + calibration_offset
where φ_reference = phase of the 1000 Hz switching reference
(computed from the switch ISR cycle counter in the firmware)
The SNR metric for the Goertzel result is:
SNR = 20 × log10(|X| / σ_noise)
Where σ_noise is the standard deviation of the ADC samples (estimated as the total block RMS minus the Goertzel tone contribution).
C.5 Phase to Bearing Conversion
The phase extracted by Goertzel is in the range -π to +π radians. Convert to bearing:
θ_raw = φ_measured × (180° / π) [range -180° to +180°]
θ_mag = θ_raw + calibration_offset_deg + array_orientation_offset_deg
if θ_mag < 0: θ_mag += 360
if θ_mag >= 360: θ_mag -= 360
θ_true = θ_mag + declination [true bearing]
# APPENDIX D — QUICK REFERENCE CARD
╔══════════════════════════════════════════════════════════════════════════════╗
║ ADF SUITE — QUICK REFERENCE — TM-ADF-001 Rev A ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ WATSON-WATT HF (esp32_adf_hf) WiFi: RF-ADFHF BT: RF-ADFHFinder ║
╠═══════════════╦════════════════════╦══════════════════════════════════════════╣
║ BAND n ║ Set band 0–10 ║ 0=160M 1=80M 2=60M 3=40M 4=30M ║
║ ║ ║ 5=20M 6=17M 7=15M 8=12M 9=10M 10=6M ║
║ STATUS ║ Print full status ║ JSON: bearing, SNR, GPS, compass ║
║ CAL START ║ Begin compass cal ║ Rotate unit 2× full turns ║
║ CAL END ║ End compass cal ║ Saves offsets to NVS ║
║ DECL f ║ Declination ║ f = degrees, + East, - West ║
║ OFFSET f ║ Bearing offset ║ f = degrees added to computed bearing ║
║ GAIN NS f ║ N-S gain ║ f = multiplier (e.g. 1.05) ║
║ GAIN EW f ║ E-W gain ║ f = multiplier ║
║ RESET ║ Software reset ║ Reboots ESP32 ║
║ HELP ║ List commands ║ ║
╠═══════════════╩════════════════════╩══════════════════════════════════════════╣
║ DOPPLER VHF (esp32_adf_vhf) WiFi: RF-ADFVHF BT: RF-ADFVHFinder ║
╠═══════════════╦════════════════════╦══════════════════════════════════════════╣
║ BAND n ║ Set band 0–4 ║ 0=2M 1=1.25M 2=70cm 3=33cm 4=20cm ║
║ STATUS ║ Full status ║ JSON: bearing, RSSI, GPS, compass ║
║ CAL START f ║ Start Dopp cal ║ f = known true bearing of transmitter ║
║ CAL ADD f ║ Add cal point ║ f = known true bearing (0, 90, 180...) ║
║ CAL END ║ End cal, save ║ Saves phase offset to NVS ║
║ SQUELCH n ║ Set squelch ║ n = RSSI threshold in dBm (neg number) ║
║ DECL f ║ Declination ║ f = degrees ║
║ HELP ║ List commands ║ ║
╠═══════════════╩════════════════════╩══════════════════════════════════════════╣
║ ROTARY LOOP (esp32_rotary_loop) WiFi: RF-ADFLOOP BT: RF-ADFLOOPFinder ║
╠═══════════════╦════════════════════╦══════════════════════════════════════════╣
║ SCAN ║ Start 360° scan ║ Takes ~3 min; updates OLED during scan ║
║ STOP ║ Halt motor ║ Motor stops at current position ║
║ HOME ║ Go to 0° ║ Returns loop to reference north mark ║
║ MOVE deg ║ Go to bearing ║ deg = 0.0–359.9, absolute ║
║ BAND n ║ Set band 0–6 ║ 0=160M 1=80M 2=60M 3=40M 4=30M ║
║ ║ ║ 5=20M 6=15M ║
║ SENSE ON ║ Enable sense ║ Activates sense relay for 180° resolution ║
║ SENSE OFF ║ Disable sense ║ ║
║ TUNE ║ Auto-tune loop ║ Sweeps varactor, finds peak RSSI (~8s) ║
║ STATUS ║ Full status ║ JSON: pos, null, depth, GPS, compass ║
║ HELP ║ List commands ║ ║
╠═══════════════╩════════════════════╩══════════════════════════════════════════╣
║ WEB API (all systems): http://192.168.4.1 ║
╠══════════════════════╦═════════════╦═══════════════════════════════════════════╣
║ GET /api/status ║ GET ║ Full JSON status ║
║ GET /api/bearing ║ GET ║ Bearing + SNR JSON subset ║
║ POST /api/band ║ POST ║ {"band":n} ║
║ POST /api/cal ║ POST ║ {"action":"start"} or {"action":"end"} ║
║ POST /api/decl ║ POST ║ {"decl":f} ║
║ POST /api/scan ║ POST ║ {"action":"scan"/"stop"/"home"} ║
║ POST /api/tune ║ POST ║ (no body) ║
╠══════════════════════╩═════════════╩═══════════════════════════════════════════╣
║ OLED LINE LAYOUT (all systems): ║
║ Line 1: MODE / BAND ║
║ Line 2: BRG: XXX°M (YYY°T) ║
║ Line 3: SNR / RSSI / NULL DEPTH + CONFIDENCE ║
║ Line 4: GPS status / MOTOR status / BT status ║
╠═════════════════════════════════════════════════════════════════════════════╣
║ FAULT CODES: ║
║ E01=COMPASS FAIL E02=GPS NO DATA E03=ADC OVERFLOW E04=CAL INVALID ║
║ E05=NS OPEN E06=EW OPEN E07=MOTOR FAULT E08=ENCODER ERR ║
║ E09=WIFI FAIL E10=NVS CORRUPT E11=ADC CLIP E12=BT FAIL ║
╠═════════════════════════════════════════════════════════════════════════════╣
║ CONFIDENCE: HI=SNR>20dB MED=SNR 10–20dB LO=SNR<10dB INVALID=no signal ║
║ TRUE BRG = MAG BRG + DECL (+ East / - West) ║
╚═════════════════════════════════════════════════════════════════════════════╝
End of TM-ADF-001 Rev A
DOCUMENT NUMBER: TM-ADF-001 Rev A CLASSIFICATION: UNCLASSIFIED PREPARED BY: TM Engineering DATE: 2026-05-02
Downloads
- NEC Model — adcock_hf_40m.nec [NEC]
- NEC Model — doppler_ring_2m.nec [NEC]
- NEC Model — phased_array_4el_rdf.nec [NEC]
- NEC Model — small_loop_hf.nec [NEC]
- OpenSCAD Model — adf_controller_box.scad [SCAD]
- OpenSCAD Model — doppler_ring_mount.scad [SCAD]
- OpenSCAD Model — rotary_loop_mount.scad [SCAD]
- OpenSCAD Model — watson_watt_antenna.scad [SCAD]
- Arduino Firmware — cyd_adf_display.ino [INO]
- Arduino Firmware — esp32_adf_hf.ino [INO]
- Arduino Firmware — esp32_adf_vhf.ino [INO]
- Arduino Firmware — esp32_rotary_loop.ino [INO]
- Schematic — doppler_vhf_ascii.txt [TXT]
- Schematic — gps_compass_integration.txt [TXT]
- Schematic — phased_array_2el_ascii.txt [TXT]
- Schematic — phased_array_4el_ascii.txt [TXT]
- Schematic — rotary_loop_servo_ascii.txt [TXT]
- Schematic — switched_array_ascii.txt [TXT]
- Schematic — watson_watt_hf_ascii.txt [TXT]