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:
Coil height (distance changes I/Q magnitude)
Ground mineral distribution (not perfectly uniform)
Electronic noise
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:
Positive offset: Reduces sensitivity to ground minerals (for very hot ground)
Negative offset: Increases sensitivity (for mild ground)
Zero offset: Neutral (exact baseline subtraction)
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)