Industrielle Fertigung
Industrielles Internet der Dinge | Industrielle Materialien | Gerätewartung und Reparatur | Industrielle Programmierung |
home  MfgRobots >> Industrielle Fertigung >  >> Manufacturing Technology >> Herstellungsprozess

Empfindlicher Arduino-Metalldetektor mit Eisen/Nichteisen-Unterscheidung

Komponenten und Zubehör

BauteilAnzahlHinweis
Arduino Nano R31Kompakt, reicht für die Signalverarbeitung
LCD 16×2 (weiß auf blau, z. B. Adafruit Standard)1Anzeige: Materialtyp + Balkendiagramm
Operationsverstärker TL081 (oder LT1677, 741)1Empfangsspulen-Verstärkung
Lautsprecher 0,25 W / 8 Ω1Akustisches Signal bei Detektion
NPN-Transistor (Allzweck, z. B. 2N2222)1Lautsprecher-Ansteuerung
Suchspulen (Sender + Empfänger)2Je 64 Windungen, 0,5 mm² CuL, 11 cm Ø, D-Form
Widerstände, Kondensatoren, Potentiometer1 SatzSiehe Schaltplan
3× Taster, 1× Potentiometer (Abgleich)1Kalibrierung, Empfindlichkeit
Stromversorgung: 2× Li-Ion (7,4 V) an Vin1~20 mA Verbrauch, lange Laufzeit

Software

Funktionsprinzip: VLF Induction Balance

Das Gerät nutzt VLF (Very Low Frequency) Induction Balance-Technologie mit zwei identischen Spulen:

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°).

Empfindlicher Arduino-Metalldetektor mit Eisen/Nichteisen-Unterscheidung

Spulenbau-Details

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

Empfindlicher Arduino-Metalldetektor mit Eisen/Nichteisen-Unterscheidung

Kernverbindungen:

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

Verbesserungspotenzial

Ein Power-MOSFET als Treiber für die Tx-Spule könnte die Empfindlichkeit deutlich steigern – wird in einem Folgeprojekt getestet.

Quellcode & Bibliothek

Schaltplan

Empfindlicher Arduino-Metalldetektor mit Eisen/Nichteisen-Unterscheidung

Herstellungsprozess

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