Manufactura industrial
Internet industrial de las cosas | Materiales industriales | Mantenimiento y reparación de equipos | Programación industrial |
home  MfgRobots >> Manufactura industrial >  >> Manufacturing Technology >> Proceso de manufactura

Construye un Detector de Metales Sensible con Arduino: Discriminación Férrico/No Férrico

Componentes y materiales

ComponenteCantidad
Arduino Nano R31
Pantalla LCD 16x2 (blanco sobre azul)1
Amplificador operacional TL081 (o LT1677, 741)1
Altavoz 0.25W, 8 ohmios1
Transistor NPN de propósito general1
Bobinas de búsqueda (ver construcción)2
Resistencias y condensadores varios1 set

Aplicaciones y servicios

Descripción del proyecto

Este proyecto muestra cómo construir un detector de metales sensible capaz de discriminar entre materiales férricos y no férricos. La sensibilidad es notable para un dispositivo relativamente simple.

Está basado en el proyecto de David Crocker (Arduino CC Forum, 2013). Decidí probar su código porque no encontré evidencia (fotos/vídeos) de que alguien lo hubiera construido y funcionara bien.

Primero monté la versión básica con el código original de GitHub para verificar el funcionamiento, luego mejoré el firmware añadiendo:

Construye un Detector de Metales Sensible con Arduino: Discriminación Férrico/No Férrico

Principio de funcionamiento: VLF Induction Balance

Utiliza tecnología VLF (Very Low Frequency) Induction Balance con dos bobinas idénticas:

El equilibrio de bobinas es crítico. Un potenciómetro anula el componente de 90° fuera de fase; el componente en fase se anula ajustando la posición relativa de las bobinas (estilo IB clásico).

Construcción de las bobinas

Construye un Detector de Metales Sensible con Arduino: Discriminación Férrico/No Férrico

Ajuste de frecuencia resonante

La frecuencia resonante paralela del circuito bobina-condensador debe determinarse (calculadora online u osciloscopio). Con las dimensiones indicadas, resulta 7.64 kHz exactos, valor ya puesto en el código.

Si tu frecuencia difiere, ajusta en el código:

#define TIMER1_TOP (249)  // Ajuste fino de frecuencia

Resultados obtenidos

Alimentación: 2 baterías Li-ion en serie (7.4 V) al pin Vin del Arduino. Consumo <20 mA → gran autonomía.

El vídeo (en el proyecto original) detalla la construcción completa.

Mejoras futuras

Se puede aumentar sensibilidad añadiendo un MOSFET de potencia para excitar la bobina Tx. Se probará en próximos vídeos.

Código

Firmware Arduino (C++)

// Induction balance metal detector
// CPU 16MHz, ADC clock 1MHz, resolución 8 bits
// Timer1: 62.5kHz square wave → Timer0 divide /8 = 7.8125kHz para bobina Tx
// 16 ciclos ADC por conversión, 8 muestras por ciclo de excitación
// 4 detectores de fase a 45° (cancela 3er armónico)
// Timer2: generación de tono para auricular
// Otras ratios Timer1 posibles (235+)

#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)       // Ajuste fino frecuencia (ver texto)
#define USE_3V3_AREF (1)       // 1 si alimentación USB (ruido en 5V), 0 si standalone con 3.3V

// Pines digitales
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;

// Pines analógicos
const int receiverInputPin = 0;
const int encoderAPin = A1;
const int encoderBpin = A2;

// Variables ISR
int16_t bins[4];
uint16_t numSamples = 0;
const uint16_t numSamplesToAverage = 1024;

// Compartidas ISR / main
volatile int16_t averages[4];
volatile uint32_t ticks = 0;
volatile bool sampleReady = false;

// Solo main
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;  // Menor = más sensible. 10 usable con bobina bien equilibrada.

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;

  // ADC trigger en Timer1 overflow
  #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;

  // Timer1: phase correct PWM, TOP=ICR1
  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);

  // Timer0: clock=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; TCNT0 = 0;
  sei();

  while (!sampleReady) {}
  misses = 0; sampleReady = false;
  Serial.begin(19200);
}

// ISR Timer1 overflow: muestreo ADC + timekeeping
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) {
    // Calibración: guarda offsets actuales
    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.print(' '); Serial.print(calib[i]);
      lcd.setCursor(0,1); lcd.print(' '); lcd.print(calib[i]); lcd.print(" ");
    }
    Serial.println();
  } else {
    // Resta calibración
    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) {
      if (phaseAverage < 0) phaseAverage += 180.0; else phaseAverage -= 180.0;
    }

    // Diagnóstico serie
    Serial.print(misses); Serial.print(' ');
    Serial.print(bin0,2); Serial.print(' ');
    Serial.print(bin1,2); Serial.print(' ');
    Serial.print(bin2,2); Serial.print(' ');
    Serial.print(bin3,2); Serial.print(" ");
    Serial.print(amp1,2); Serial.print(' ');
    Serial.print(amp2,2); Serial.print(' ');
    Serial.print(phase1,2); Serial.print(' ');
    Serial.print(phase2,2); Serial.print(" ");
    if (ampAverage >= 0) Serial.print(' ');
    Serial.print(ampAverage,1); Serial.print(' ');
    lcd.setCursor(0,0); lcd.print(" "); lcd.print(ampAverage);
    lcd.setCursor(0,1); lbg.drawValue(ampAverage, max_ampAverage);
    if (phaseAverage >= 0) Serial.print(' ');
    Serial.print((int)phaseAverage);

    // Clasificación y alerta
    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.print('!'); temp -= threshold/2; }
    }
    Serial.println();
  }
  while (ticks - oldTicks < 8000) {}
}

Librería LcdBarGraph

Disponible en el repositorio del proyecto (solo descarga, sin vista previa).

Esquemático

Construye un Detector de Metales Sensible con Arduino: Discriminación Férrico/No Férrico

Proyecto patrocinado por PCBgogo.

Proceso de manufactura

  1. Construya un Detector de Llanto de Bebé Económico con Raspberry Pi – Guía DIY
  2. Construye un reloj Numitron retro con Arduino: Simple, fiable y eficiente energéticamente
  3. Construye un Juego Giroscópico en Tiempo Real con Arduino Nano y Sensor MPU-6050
  4. Detector de terremotos DIY con ADXL335 – Acelerómetro 3 ejes de baja potencia
  5. Construye un voltímetro DIY con Arduino y pantalla Nokia 5110 – Guía paso a paso
  6. MobBob: Robot Arduino Impreso en 3D, Controlado por Smartphone Android
  7. Construir una rueda medidora DIY precisa con Arduino Nano y encoder rotativo
  8. Crear un medidor IoT web con Arduino, Yaler e IFTTT
  9. Construye un monitor de calidad del aire preciso con el sensor Sharp GP2Y1010AU0F
  10. Contador Geiger Portátil con Arduino Nano: Construye tu Detector de Radiación
  11. Juego Pong 1D con Arduino Nano y tira LED WS2812: Tutorial paso a paso