GROUND BALANCE PROCESS
Mineralized Soil Cancellation for Metal Detectors
THE PROBLEM: Why Ground Balance is Needed

Without Ground Balance

Ground minerals (iron oxides, salts, clay) produce constant signals that:

  • Overwhelm weak target signals
  • Create false "metal" responses
  • Vary with coil height and ground type
  • Change with temperature and moisture

Result: Can't detect small/deep targets through the noise

With Ground Balance

Ground signal is nulled by measuring and subtracting the baseline:

  • Only target signals remain
  • Improved depth on small targets
  • Stable operation over varying ground
  • Reduced false signals

Result: Clean target detection in mineralized soil

Key Insight

Ground minerals produce a constant I/Q vector at each frequency. By measuring this vector and subtracting it from all readings, we cancel the ground signal while preserving target signals.

FOUR GROUND BALANCE MODES
Mode Description Best For Behavior
OFF No ground balance applied Clean beaches, air tests Raw signal passed through
MANUAL "Pump and set" - user captures ground baseline Stable ground, maximum control Fixed baseline until manually reset
AUTO_TRACKING Automatically adapts to ground changes Variable ground, ease of use Slow IIR filter (α=0.0005), freezes on targets
MANUAL_TRACKING Manual preset + auto-tracking adjustments Hot ground with drift Starts with manual, then slowly tracks changes
GroundBalanceManager.kt:9-14 - GroundBalanceMode enum
MANUAL MODE: "Pump and Set" Process
1
User starts capture
Calls startManualCapture(), clears previous samples
GroundBalanceManager.kt:81
2
User "pumps" coil
Moves coil up/down over ground 10 times
Each audio callback captures IQ analysis
Samples stored in captureSamples list
3
User stops capture
Calls stopManualCapture()
GroundBalanceManager.kt:89
4
Calculate average baseline
Average I/Q components across all 10 samples, per frequency
GroundBalanceManager.kt:267 - averageCapturedSamples()
5
Store baseline
manualBaseline = List<GroundBalancePoint>
One point per frequency with I, Q, amplitude, phase
Averaging Formula (per frequency):

For each frequency f:
  avgI[f] = (I₁ + I₂ + ... + I₁₀) / 10
  avgQ[f] = (Q₁ + Q₂ + ... + Q₁₀) / 10
  avgAmp[f] = √(avgI² + avgQ²)
  avgPhase[f] = atan2(avgQ, avgI)

Result: GroundBalancePoint(freq, avgI, avgQ, avgAmp, avgPhase)

Why Pump the Coil?

Pumping averages out variations in:

Result: More stable baseline = better ground cancellation

AUTO-TRACKING MODE: Continuous Adaptation
IIR Low-Pass Filter
(α = 0.0005 = 0.05%)
Tracking Update Formula (every audio callback):

baseline[n] = α × current[n] + (1 - α) × baseline[n-1]

Where:
  α = 0.0005 (very slow tracking)
  current = latest IQ measurement
  baseline = tracking baseline

Time Constant:
At 23.2 Hz callback rate: τ ≈ 1/(23.2 × 0.0005) ≈ 86 seconds
Takes ~86 seconds to adapt 63% to ground changes
GroundBalanceManager.kt:188 - updateTrackingBaseline()

Target Detection & Freeze

Problem: If tracking continues during target detection, it will try to "null out" the target!

Solution: Freeze tracking when strong signal detected

if (maxAmplitude > 0.3) {
  trackingFrozen = true
  // Don't update baseline
} else {
  trackingFrozen = false
  // Continue slow tracking
}
GroundBalanceManager.kt:189-196
Auto-Tracking Behavior:

• Initializes on first reading
• Slowly follows ground changes
• Freezes when amplitude > 0.3
• Resumes tracking when target passes
BASELINE SUBTRACTION: Vector Math with Offset
I/Q Vector Subtraction (Before/After Ground Balance)
I Q Ground Baseline Signal + Ground Target Only Result: Green vector = Blue - Red Ground signal cancelled, target signal preserved
Subtraction Formula (with offset adjustment):

1. Apply offset as phase rotation to baseline:
  offsetRadians = (offset / 50) × (π/4)
  rotatedI = baseI × cos(offset) - baseQ × sin(offset)
  rotatedQ = baseI × sin(offset) + baseQ × cos(offset)

2. Subtract rotated baseline from current signal:
  newI = currentI - rotatedI
  newQ = currentQ - rotatedQ

3. Calculate new amplitude and phase:
  newAmplitude = √(newI² + newQ²)
  newPhase = atan2(newQ, newI)
GroundBalanceManager.kt:228 - subtractBaseline()

GB Offset: User Fine-Tuning

Offset Range: -50 to +50

Effect: Rotates the baseline vector by ±45°

Use Case:

This allows user to optimize the "null point" for their specific ground conditions.

COMPLETE GROUND BALANCE FLOW
IQ Analysis from Demodulator
List<ToneAnalysis>
applyGroundBalance(analysis)
Check Mode
OFF / MANUAL / AUTO /
MANUAL_TRACKING
Update Tracking?
If AUTO mode:
Update baseline (if not frozen)
Get Baseline
Manual or Tracking
baseline vector
Subtract Baseline with Offset
(Vector subtraction in I/Q space)
Ground-Balanced ToneAnalysis
(Clean target signal)
To VDI Calculator
GroundBalanceManager.kt:154 - applyGroundBalance()
MODE COMPARISON
Feature OFF MANUAL AUTO_TRACKING MANUAL_TRACKING
Setup Required None Pump coil 10x None (auto-init) Pump coil, then auto
Adapts to Changes N/A No Yes (slow) Yes (slow)
Target Freeze N/A N/A Yes (amp > 0.3) Yes (amp > 0.3)
Best For Air tests, beach Stable ground Variable ground Hot + drifting ground
Skill Level Any Intermediate Beginner Advanced
KEY PARAMETERS
Parameter Value Effect
Capture Samples 10 pumps More samples = better averaging, slower setup
Tracking Alpha (α) 0.0005 Smaller = slower tracking, less target null-out
Freeze Threshold 0.3 amplitude Higher = more aggressive tracking near targets
Offset Range -50 to +50 ±45° phase rotation of baseline vector
Time Constant (τ) ~86 seconds How fast auto-tracking adapts (63% convergence)