Empfindlicher Arduino-Metalldetektor mit Eisen/Nichteisen-Unterscheidung
Komponenten und Zubehör
| Bauteil | Anzahl | Hinweis |
|---|---|---|
| Arduino Nano R3 | 1 | Kompakt, reicht für die Signalverarbeitung |
| LCD 16×2 (weiß auf blau, z. B. Adafruit Standard) | 1 | Anzeige: Materialtyp + Balkendiagramm |
| Operationsverstärker TL081 (oder LT1677, 741) | 1 | Empfangsspulen-Verstärkung |
| Lautsprecher 0,25 W / 8 Ω | 1 | Akustisches Signal bei Detektion |
| NPN-Transistor (Allzweck, z. B. 2N2222) | 1 | Lautsprecher-Ansteuerung |
| Suchspulen (Sender + Empfänger) | 2 | Je 64 Windungen, 0,5 mm² CuL, 11 cm Ø, D-Form |
| Widerstände, Kondensatoren, Potentiometer | 1 Satz | Siehe Schaltplan |
| 3× Taster, 1× Potentiometer (Abgleich) | 1 | Kalibrierung, Empfindlichkeit |
| Stromversorgung: 2× Li-Ion (7,4 V) an Vin | 1 | ~20 mA Verbrauch, lange Laufzeit |
Software
- Arduino IDE
- Bibliothek:
LcdBarGraph(für Balkendiagramm auf LCD)
Funktionsprinzip: VLF Induction Balance
Das Gerät nutzt VLF (Very Low Frequency) Induction Balance-Technologie mit zwei identischen Spulen:
- Sender-Spule (Tx): Erzeugt ein 7,64 kHz Magnetfeld
- Empfänger-Spule (Rx): Nimmt Änderungen durch Metallobjekte auf
Bei perfekter Balance heben sich die Signale auf. Metall in der Nähe stört die Balance → detektierbares Signal. Die Phasenverschiebung unterscheidet Eisen (ferromagnetisch, Phasenverschiebung ≈ 0°) von Nichteisen (diamagnetisch/paramagnetisch, negative Phasenverschiebung, z. B. Kupfer −90°).

Spulenbau-Details
- Körper: 11 cm Durchmesser (z. B. PVC-Rohr oder 3D-Druck)
- Draht: 0,5 mm² lackierter Kupferdraht, 64 Windungen
- Form: D-förmig (flache Seite zur Gegenüberstellung)
- Abschirmung: Alufolie umwickelt, mit verzinntem Kupferdraht kontaktiert – kleiner Spalt lassen! (sonst Kurzschlusswindung)
- Beide Spulen auf gemeinsamer Platte befestigt, Abstand justierbar für Phasenabgleich
Resonanzfrequenz einstellen
Parallelresonanz der Tx-Spule mit Kondensator: ca. 7,64 kHz (bei obigen Maßen). Im Code:
#define TIMER1_TOP (259) // Feinjustage der Frequenz
Abweichende Frequenz → TIMER1_TOP anpassen (Oszilloskop oder Frequenzzähler hilfreich).
Schaltung

Kernverbindungen:
- Pin 4 (T0) ↔ Pin 9 (Timer-Verknüpfung)
- Pin 5 → Vorwiderstand → Tx-Spule + Abstimmkondensator
- Rx-Spule → OpAmp TL081 → Ausgang an A0 (auf ~Vcc/2 biasen)
- Lautsprecher an Pin 3 (OCR2B / Timer2) via Transistor
- LCD: RS=6, EN=7, D4=10, D5=11, D6=12, D7=13
- Kalibtrier-Taster an Pin 2 (Pullup)
Arduino-Code (Hauptlogik)
Der Code nutzt Timer1 (62,5 kHz) und Timer0 (7,8125 kHz für Tx), ADC getriggert von Timer1-Overflow, 4 Phasen-Detektoren (0°, 45°, 90°, 135°) zur 3. Harmonischen-Unterdrückung.
#include <LiquidCrystal.h>
#include <LcdBarGraph.h>
#define max_ampAverage 200
LiquidCrystal lcd(6, 7, 10, 11, 12, 13);
LcdBarGraph lbg(&lcd, 16, 0, 1);
#define TIMER1_TOP (259)
#define USE_3V3_AREF (1) // 1 bei USB-Strom, 0 bei eigenem 5V-Regler
// Pin-Definitionen
const int encoderButtonPin = 2;
const int earpiecePin = 3;
const int T0InputPin = 4;
const int coilDrivePin = 5;
const int LcdPowerPin = 8;
const int T0OutputPin = 9;
const int receiverInputPin = 0;
const int encoderAPin = A1;
const int encoderBpin = A2;
// ISR-Variablen
int16_t bins[4];
uint16_t numSamples = 0;
const uint16_t numSamplesToAverage = 1024;
volatile int16_t averages[4];
volatile uint32_t ticks = 0;
volatile bool sampleReady = false;
int16_t calib[4];
volatile uint8_t lastctr;
volatile uint16_t misses = 0;
const double halfRoot2 = sqrt(0.5);
const double radiansToDegrees = 180.0 / PI;
const float phaseAdjust = (45.0 * 32.0) / (float)(TIMER1_TOP + 1);
float threshold = 5.0; // Empfindlichkeit (kleiner = empfindlicher)
void setup() {
lcd.begin(16, 2);
pinMode(encoderButtonPin, INPUT_PULLUP);
pinMode(T0OutputPin, OUTPUT); digitalWrite(T0OutputPin, LOW);
pinMode(coilDrivePin, OUTPUT); digitalWrite(coilDrivePin, LOW);
// Timer0 stoppen (Arduino Core)
cli(); TCCR0B = 0; TIMSK0 = 0; TIFR0 = 0x07;
// ADC: Auto-Trigger auf Timer1 Overflow, 1 MHz ADC-Takt, 8-bit (ADLAR)
#if USE_3V3_AREF
ADMUX = (1 << ADLAR); // AREF = 3,3V
#else
ADMUX = (1 << REFS0) | (1 << ADLAR); // AVcc Referenz
#endif
ADCSRB = (1 << ADTS2) | (1 << ADTS1);
ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADPS2);
DIDR0 = 1;
// Timer1: Phasenkorrekt PWM, TOP=ICR1, Prescaler=1
TCCR1A = (1 << COM1A1) | (1 << WGM11);
TCCR1B = (1 << WGM12) | (1 << WGM13) | (1 << CS10);
OCR1A = TIMER1_TOP / 2; ICR1 = TIMER1_TOP;
TIMSK1 = (1 << TOIE1);
// Timer0: Externer Takt (T0), Fast PWM, TOP=7
TCCR0A = (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
TCCR0B = (1 << CS00) | (1 << CS01) | (1 << CS02) | (1 << WGM02);
OCR0A = 7; OCR0B = 3;
sei();
while (!sampleReady) {} // Ersten Sample verwerfen
misses = 0; sampleReady = false;
Serial.begin(19200);
}
ISR(TIMER1_OVF_vect) {
++ticks;
uint8_t ctr = TCNT0;
int16_t val = (int16_t)(uint16_t)ADCH;
if (ctr != ((lastctr + 1) & 7)) ++misses;
lastctr = ctr;
int16_t *p = &bins[ctr & 3];
if (ctr < 4) { *p += val; if (*p > 15000) *p = 15000; }
else { *p -= val; if (*p < -15000) *p = -15000; }
if (ctr == 7) {
if (++numSamples == numSamplesToAverage) {
numSamples = 0;
if (!sampleReady) {
memcpy((void*)averages, bins, sizeof(averages));
sampleReady = true;
}
memset(bins, 0, sizeof(bins));
}
}
}
void loop() {
while (!sampleReady) {}
uint32_t oldTicks = ticks;
if (digitalRead(encoderButtonPin) == LOW) { // Kalibrierung
for (int i = 0; i < 4; ++i) calib[i] = averages[i];
sampleReady = false;
lcd.setCursor(0,0); lcd.print("Calibrating... ");
Serial.print("Calibrated: ");
for (int i = 0; i < 4; ++i) { Serial.print(' '); Serial.print(calib[i]); }
Serial.println();
} else {
for (int i = 0; i < 4; ++i) averages[i] -= calib[i];
const double f = 200.0;
double bin0 = (averages[0] + halfRoot2 * (averages[1] - averages[3])) / f;
double bin1 = (averages[1] + halfRoot2 * (averages[0] + averages[2])) / f;
double bin2 = (averages[2] + halfRoot2 * (averages[1] + averages[3])) / f;
double bin3 = (averages[3] + halfRoot2 * (averages[2] - averages[0])) / f;
sampleReady = false;
double amp1 = sqrt(bin0*bin0 + bin2*bin2);
double amp2 = sqrt(bin1*bin1 + bin3*bin3);
double ampAverage = (amp1 + amp2) / 2.0;
double phase1 = atan2(bin0, bin2) * radiansToDegrees + 45.0;
double phase2 = atan2(bin1, bin3) * radiansToDegrees;
if (phase1 > phase2) { double t=phase1; phase1=phase2; phase2=t; }
double phaseAverage = ((phase1 + phase2) / 2.0) - phaseAdjust;
if (phase2 - phase1 > 180.0) {
phaseAverage = (phaseAverage < 0) ? phaseAverage + 180.0 : phaseAverage - 180.0;
}
// Anzeige
lcd.setCursor(0,0); lcd.print(" "); lcd.print(ampAverage);
lcd.setCursor(0,1); lbg.drawValue(ampAverage, max_ampAverage);
// Entscheidung: Ferrous vs Non-ferrous
if (ampAverage >= threshold) {
if (phaseAverage < -20.0) { // Non-ferrous
lcd.setCursor(0,0); lcd.print("NonFerous ");
tone(3, map(ampAverage, 10, 200, 100, 1500), 120);
} else { // Ferrous
lcd.setCursor(0,0); lcd.print("Ferrous ");
tone(3, map(ampAverage, 10, 200, 100, 1500), 120);
}
}
}
while (ticks - oldTicks < 8000) {} // Loop-Timing ~8 ms
}
Leistung & Ergebnisse
- Stabilität: Ohne Metall vollständig ruhig
- Reichweite: Metalldeckel (15 cm Ø) > 30 cm; große Objekte > 40–50 cm
- Kleine Münze: ~15 cm in Luft
- Stromaufnahme: < 20 mA bei 7,4 V → sehr lange Batterielaufzeit
Verbesserungspotenzial
Ein Power-MOSFET als Treiber für die Tx-Spule könnte die Empfindlichkeit deutlich steigern – wird in einem Folgeprojekt getestet.
Quellcode & Bibliothek
- Arduino-Code: Siehe oben (vollständig)
LcdBarGraph-Bibliothek: Download über Arduino Library Manager oder GitHub
Schaltplan

Herstellungsprozess
- Günstiger Raspberry Pi Babyphone mit Schreierkennung – DIY-Anleitung
- Retro-Numitron-Uhr mit Arduino bauen: Einfach, zuverlässig & energieeffizient
- Echtzeit-Gyroskop-Spiel mit Arduino Nano & MPU-6050
- DIY ADXL335 Erdbeben-Detektor – Low-Power 3-Achsen-Beschleunigungsmesser-Projekt
- DIY-Voltmeter mit Arduino und Nokia 5110 Display – Schritt-für-Schritt-Anleitung
- MobBob: Eigenen Arduino-Roboter bauen, per Android-Smartphone steuern
- Präzises DIY-Messrad mit Arduino Nano und Drehgeber
- Web-fähigen IoT-Messer mit Arduino, Yaler und IFTTT bauen
- Präzisen Luftqualitäts-Monitor mit Sharp GP2Y1010AU0F Sensor bauen
- Handheld Geigerzähler mit Arduino Nano selbst bauen