Industriële fabricage
Industrieel internet der dingen | Industriële materialen | Onderhoud en reparatie van apparatuur | Industriële programmering |
home  MfgRobots >> Industriële fabricage >  >> Manufacturing Technology >> Productieproces

Bouw een gevoelige Arduino-metaaldetector met ferro/non-ferro discriminatie

Onderdelen en benodigdheden

Onderdeel Aantal Opmerkingen
Arduino Nano R3 1
Adafruit Standard LCD - 16x2 Wit op Blauw 1
TL081 Operationele Versterker IC 1
Luidspreker: 0.25W, 8 ohm 1
Algemene NPN-transistor 1
Zoekspoelen 2
Weerstanden, Condensatoren 1 set

Apps en online diensten

  • Arduino IDE

Over dit project

Deze keer laat ik zien hoe je een gevoelige metaaldetector bouwt die ook kan discrimineren tussen ferrode (ijzerhoudende) en non-ferrode materialen. De gevoeligheid is bevredigend, gezien de relatieve eenvoud van het apparaat.

Dit project wordt gesponsord door PCBgogo:

https://www.pcbgogo.com/promo/from_MirkoPavleskiMK

Dit is een voortzetting van David Crocker's project, gepresenteerd op het Arduino CC Forum in 2013. Ik besloot de code te testen omdat ik geen bewijs (foto of video) kon vinden dat deze metaaldetector door iemand was gemaakt en goed werkte.

Eerst maakte ik een basismodel met de code van GitHub, om de functionaliteit te verifiëren. Daarna breidde ik de code uit met een geluidssignaal en visuele informatie op een 16x2 LCD-display over het type gedetecteerd object (Ferro of Non-ferro) en een balkgrafiek voor de nabijheid van gedetecteerde objecten.

Het apparaat is heel simpel te bouwen en bestaat uit slechts een paar componenten:

  • Arduino Nano microcontroller
  • Operationele versterker (in mijn geval LT1677, maar TL081 of 741 werkt ook)
  • Enkele weerstanden en condensatoren
  • Kleine transistor en luidspreker
  • LCD-display
  • 3 schakelaars
  • Potentiometer
  • Batterij
  • Zoekspoelen
Bouw een gevoelige Arduino-metaaldetector met ferro/non-ferro discriminatie
Onderdelen van de metaaldetector

Dit is een VLF (Very Low Frequency) Induction Balance detectietechnologie en bevat twee identieke spoelen: een zender- en een ontvangerspoel. Zoals bij alle induction balance detectors, is de spoelbalans zeer kritiek. De potentiometer wordt gebruikt om de kleine 90 graden uit-fase component van het signaal op nul te zetten (de in-fase component wordt genuleerd door de relatieve plaatsing van de spoelen aan te passen in typische IB-detector-stijl). Elke spoel is gewikkeld op een 11 cm diameter met 64 windingen van 0,5 mm² geëmailleerd koperdraad in een D-vorm, omwonden met tape, afgeschermd met aluminiumfolie gebonden met vergulde koperdraad (met een kleine opening zodat de scherming niet als een kortgesloten winding gedraagt zich), en vastgezet met kabelbinders op een plastic plaat.

Bouw een gevoelige Arduino-metaaldetector met ferro/non-ferro discriminatie
Constructie van de zoekspoelen

We moeten eerst de parallelle resonantiefrequentie van de primaire spoel-condensator schakeling bepalen met een van de vele online calculators. Ik meet het met een oscilloscoop, maar als je je houdt aan de bovenstaande afmetingen, zal het precies 7,64 kHz zijn, zodat je de waarde direct in de code kunt invoeren. Bij een andere resonantiefrequentie moet de code worden aangepast in de regel:

#define TIMER1_TOP (249) // fine-tune the frequency

Zoals je in de video kunt zien, zijn de resultaten verrassend goed. Zonder metaal is het apparaat perfect stabiel. De bereik is redelijk groot: een metalen deksel van 15 cm diameter wordt op meer dan 30 cm gedetecteerd. Grotere metalen objecten op meer dan 40-50 cm. Een kleine munt kan op 15 cm afstand in de lucht worden gedetecteerd. Ik gebruik twee lithium-batterijen in serie (7,4 volt) aangesloten op de Vin-ingang van de Arduino. De stroomverbruik overschrijdt niet de 20 mA, dus de batterijen duren heel lang. De video beschrijft de constructie van het hele apparaat in detail.

Dit zijn nog voorlopige resultaten. Er is mogelijkheid om de gevoeligheid aanzienlijk te verbeteren door een power MOSFET-transistor toe te voegen voor het aansturen van de zenderspoel, maar ik zal dat in een volgende video testen en presenteren.

Code

  • Arduino-code
  • LcdBarGraph bibliotheek

Arduino-code

// Induction balance metaaldetector
// CPU draait op 16MHz, ADC klok op 1MHz. ADC resolutie gereduceerd tot 8 bits op deze snelheid.
// Timer 1 deelt systeemklok door ~256 voor 62.5kHz blokgolf.
// Timer 0 deelt timer 1 uitgang door 8, geeft 7.8125kHz signaal voor zenderspoel.
// ADC implementeert vier fasegevoelige detectoren op 45 gradus intervallen.
// Timer 2 genereert toon voor oortje/hoofdtelefoon.

#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) // kan worden aangepast om frequentie finetunen
#define USE_3V3_AREF (1) // 1 als Arduino via USB gevoed wordt, 0 voor embedded atmega328p zonder 3.3V

// Digitale pin definities
const int debugTxPin = 1;
const int encoderButtonPin = 2;
const int earpiecePin = 3;
const int T0InputPin = 4;
const int coilDrivePin = 5;
const int LcdRsPin = 6;
const int LcdEnPin = 7;
const int LcdPowerPin = 8;
const int T0OutputPin = 9;
const int lcdD4Pin = 10;
const int lcdD5Pin = 11;
const int LcdD6Pin = 12;
const int LcdD7Pin = 13;

// Analog pin definities
const int receiverInputPin = 0;
const int encoderAPin = A1;
const int encoderBpin = A2;

// Variabelen alleen door ISR
int16_t bins[4];
uint16_t numSamples = 0;
const uint16_t numSamplesToAverage = 1024;

// Variabelen gedeeld tussen ISR en main
volatile int16_t averages[4];
volatile uint32_t ticks = 0;
volatile bool sampleReady = false;

// Variabelen alleen buiten ISR
int16_t calib[4];
volatile uint8_t lastctr;
volatile uint16_t misses = 0;

const double halfRoot2 = sqrt(0.5);
const double quarterPi = 3.1415927/4.0;
const double radiansToDegrees = 180.0/3.1415927;

const float phaseAdjust = (45.0 * 32.0)/(float)(TIMER1_TOP + 1);
float threshold = 5.0; // lager = gevoeliger. 10 is net bruikbaar met goed gebalanceerde spoel.

void setup() {
  lcd.begin(16, 2);
  pinMode(encoderButtonPin, INPUT_PULLUP);
  digitalWrite(T0OutputPin, LOW);
  pinMode(T0OutputPin, OUTPUT);
  digitalWrite(coilDrivePin, LOW);
  pinMode(coilDrivePin, OUTPUT);
  
  cli();
  TCCR0B = 0; TIMSK0 = 0; TIFR0 = 0x07;
  
  #if USE_3V3_AREF
    ADMUX = (1 << ADLAR);
  #else
    ADMUX = (1 << REFS0) | (1 << ADLAR);
  #endif
  ADCSRB = (1 << ADTS2) | (1 << ADTS1);
  ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADPS2);
  DIDR0 = 1;
  
  TCCR1A = (1 << COM1A1) | (1 << WGM11);
  TCCR1B = (1 << WGM12) | (1 << WGM13) | (1 << CS10);
  TCCR1C = 0;
  OCR1AH = (TIMER1_TOP/2 >> 8); OCR1AL = (TIMER1_TOP/2 & 0xFF);
  ICR1H = (TIMER1_TOP >> 8); ICR1L = (TIMER1_TOP & 0xFF);
  TCNT1H = 0; TCNT1L = 0;
  TIFR1 = 0x07;
  TIMSK1 = (1 << TOIE1);
  
  TCCR0A = (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
  TCCR0B = (1 << CS00) | (1 << CS01) | (1 << CS02) | (1 << WGM02);
  OCR0A = 7; OCR0B = 3; TCNT0 = 0;
  sei();
  
  while (!sampleReady) {}
  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) {
    ++numSamples;
    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) {
    // Kalibratieknop ingedrukt
    for (int i = 0; i < 4; ++i) { calib[i] = averages[i]; }
    sampleReady = false;
    Serial.print("Calibrated: ");
    lcd.setCursor(0,0); lcd.print("Calibrating... ");
    for (int i = 0; i < 4; ++i) {
      Serial.write(' '); Serial.print(calib[i]);
      lcd.setCursor(0,1); lcd.print(' '); lcd.print(calib[4]); lcd.print(" ");
    }
    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 temp = phase1; phase1 = phase2; phase2 = temp; }
    double phaseAverage = ((phase1 + phase2)/2.0) - phaseAdjust;
    if (phase2 - phase1 > 180.0) {
      if (phaseAverage < 0.0) phaseAverage += 180.0;
      else phaseAverage -= 180.0;
    }
    
    // Diagnostische output
    Serial.print(misses); Serial.write(' ');
    if (bin0 >= 0.0) Serial.write(' '); Serial.print(bin0, 2); Serial.write(' ');
    if (bin1 >= 0.0) Serial.write(' '); Serial.print(bin1, 2); Serial.write(' ');
    if (bin2 >= 0.0) Serial.write(' '); Serial.print(bin2, 2); Serial.write(' ');
    if (bin3 >= 0.0) Serial.write(' '); Serial.print(bin3, 2); Serial.print(" ");
    Serial.print(amp1, 2); Serial.write(' '); Serial.print(amp2, 2); Serial.write(' ');
    if (phase1 >= 0.0) Serial.write(' '); Serial.print(phase1, 2); Serial.write(' ');
    if (phase2 >= 0.0) Serial.write(' '); Serial.print(phase2, 2); Serial.print(" ");
    
    if (ampAverage >= 0.0) Serial.write(' '); Serial.print(ampAverage, 1); Serial.write(' ');
    lcd.setCursor(0,0); lcd.print(" "); lcd.print(ampAverage);
    lcd.setCursor(0,1); lbg.drawValue(ampAverage, max_ampAverage);
    if (phaseAverage >= 0.0) Serial.write(' '); Serial.print((int)phaseAverage);
    
    // Bepalen wat er gedetecteerd is
    if (ampAverage >= threshold) {
      if (phaseAverage < -20.0) {
        Serial.print(" Non-ferrous");
        lcd.setCursor(0,0); lcd.print("NonFerous ");
      } else {
        Serial.print(" Ferrous");
        lcd.setCursor(0,0); lcd.print("Ferrous ");
      }
      float temp = ampAverage;
      int thisPitch = map(temp, 10, 200, 100, 1500);
      tone(3, thisPitch, 120);
      while (temp > threshold) { Serial.write('!'); temp -= (threshold/2); }
    }
    Serial.println();
  }
  while (ticks - oldTicks < 8000) {}
}

LcdBarGraph bibliotheek

Geen preview beschikbaar (alleen download).

Schema's

Bouw een gevoelige Arduino-metaaldetector met ferro/non-ferro discriminatie
Elektrisch schema

Productieproces

  1. Een Budget-vriendelijke Raspberry Pi Babyhuil-detector Bouwen – DIY Handleiding
  2. Bouw een retro Numitron-klok met Arduino: Eenvoudig, betrouwbaar en energiezuinig
  3. Bouw een real-time gyroscoop-spel met Arduino Nano & MPU-6050 sensor
  4. DIY ADXL335 Aardbevingendetector – Laag-vermogen 3-as Versnellingsmeter Project
  5. Bouw een DIY Voltmeter met Arduino en Nokia 5110 Display – Stap-voor-Stap Handleiding
  6. MobBob: Bouw Je Eigen Arduino Robot, Naadloos Bestuurd via Android Smartphone
  7. Een nauwkeurig meetwieltje bouwen met Arduino Nano en draaiencoder
  8. Web-IoT Meter Bouwen met Arduino, Yaler en IFTTT
  9. Precieze Luchtkwaliteit Monitor met Sharp GP2Y1010AU0F Sensor
  10. Bouw een handgehoude Geiger-teller met Arduino Nano
  11. 1D Pong-spel met Arduino en WS2812 LED-strip – Stap-voor-stap DIY handleiding