Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Сборка чувствительного металлодетектора на Arduino с дискриминацией ферро/неферро

Компоненты и материалы

Arduino Nano R3 1
LCD-дисплей 16x2 (белый на синем) 1
Операционный усилитель TL081 (или LT1677, 741) 1
Динамик 0.25Вт, 8 Ом 1
Транзистор NPN общего назначения 1
Поисковые катушки (2 шт.) 2
Набор резисторов и конденсаторов 1

Приложения и онлайн-сервисы

О проекте

В этот раз я покажу, как сделать чувствительный металлодетектор, способный различать ферромагнитные и неферромагнитные материалы. Чувствительность вполне удовлетворительна для такого относительно простого устройства.

Проект спонсируется PCBgogo: https://www.pcbgogo.com/promo/from_MirkoPavleskiMK

Это продолжение проекта Дэвида Крокера (David Crocker), представленного на форуме Arduino CC в 2013 году. Я решил протестировать его код, так как не нашел доказательств (фото или видео), что этот металлодетектор кто-то собрал и он работает.

Сначала я собрал базовую версию с кодом с GitHub, чтобы убедиться в работоспособности, а затем улучшил код: добавил звуковой сигнал и вывод на 16x2 LCD информации о типе обнаруженного объекта (ферромагнитный или неферромагнитный) и шкалу приближения.

Устройство очень простое и состоит из нескольких компонентов:

Это металлодетектор на технологии VLF (очень низкая частота) типа Induction Balance. Он содержит две идентичные катушки: передающую и принимающую. Как и во всех детекторах индукционного баланса, баланс катушек критически важен. Потенциометр используется для компенсации малой составляющей сигнала, сдвинутой на 90 градусов по фазе (вфазная составляющая компенсируется регулировкой взаимного расположения катушек в типичном стиле IB-детекторов).

Каждая катушка наматывается на каркас 11 см диаметром, 64 витка эмалированной медной проволоки сечением 0,5 мм² в форме буквы D. Катушки обматываются изолентой, экранируются алюминиевой фольгой, закрепленной оловянной медной проволокой (с небольшим зазором, чтобы экран не вел себя как закороченный виток), и крепятся на пластиковую пластину стяжками.

Определение резонансной частоты

Сначала нужно определить параллельную резонансную частоту контура первичной катушки-конденсатора с помощью онлайн-калькуляторов. Я измерял осциллографом, но если соблюдать указанные размеры, частота будет ровно 7,64 кГц, поэтому это значение можно прямо прописать в коде. При другом значении резонансной частоты нужно внести соответствующее изменение в код:

#define TIMER1_TOP (249) // тонкая настройка частоты

Как видно на видео, результаты удивительно хороши. Без металла устройство абсолютно стабильно. Дальность обнаружения довольно большая: металлическая крышка диаметром 15 см обнаруживается на расстоянии более 30 см. Крупные металлические объекты — на 40–50 см. Маленькую монету можно обнаружить на 15 см в воздухе. Питание — две литий-ионные батареи последовательно (7,4 В), подключенные к входу Vin Arduino. Потребление не превышает 20 мА, поэтому батарей хватает на очень долгое время. Видео подробно описывает сборку всего устройства.

Это предварительные результаты. Есть возможность значительно повысить чувствительность, добавив полевой транзистор MOSFET для управления Tx-катушкой, но это будет протестировано и представлено в следующих видео.

Код

Arduino-скетч

// Induction balance metal detector
// CPU 16MHz, ADC clock 1MHz, 8-bit resolution
// Timer 1: ~62.5kHz square wave -> drives timer 0 and triggers ADC
// Timer 0: divides by 8 -> 7.8125kHz for transmit coil
// 16 ADC clocks per conversion (13.5 actual), 8 samples per coil drive cycle
// 4 phase-sensitive detectors at 45° intervals (cancels 3rd harmonic)
// Timer 2: tone generation for earpiece

#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) // fine-tune frequency
#define USE_3V3_AREF (1) // 1 for USB power (3.3V ref), 0 for standalone

// Digital pins
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 pins
const int receiverInputPin = 0;
const int encoderAPin = A1;
const int encoderBpin = A2;

// ISR variables
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 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; // lower = higher sensitivity

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) {
    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 {
    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;
    }
    
    // Serial output for diagnostics
    Serial.print(misses); Serial.print(' ');
    if (bin0>=0) Serial.print(' '); Serial.print(bin0,2); Serial.print(' ');
    if (bin1>=0) Serial.print(' '); Serial.print(bin1,2); Serial.print(' ');
    if (bin2>=0) Serial.print(' '); Serial.print(bin2,2); Serial.print(' ');
    if (bin3>=0) Serial.print(' '); Serial.print(bin3,2); Serial.print(' ');
    Serial.print(amp1,2); Serial.print(' '); Serial.print(amp2,2); Serial.print(' ');
    if (phase1>=0) Serial.print(' '); Serial.print(phase1,2); Serial.print(' ');
    if (phase2>=0) 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);
    
    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) {}
}

Библиотека LcdBarGraph

Библиотека доступна для скачивания (превью недоступно).

Схемы

Схема подключения прилагается в разделе Schematics оригинального проекта.

Производственный процесс

  1. Создание бюджетного детектора плача ребенка на Raspberry Pi — DIY-руководство
  2. Ретро-часы на индикаторах Numitron с Arduino: просто, надёжно, экономно
  3. Создание игры в реальном времени на Arduino Nano с гироскопом MPU-6050 и OLED-дисплеем
  4. DIY детектор землетрясений на ADXL335 с усилением ОУ OP07
  5. Сборка вольтметра на Arduino с дисплеем Nokia 5110 — Пошаговое руководство
  6. MobBob: Самостоятельная сборка Arduino-робота под управлением Android-смартфона
  7. Сборка точного измерительного колеса на Arduino Nano и вращательном энкодере
  8. Веб-управляемый IoT-индикатор на Arduino, Yaler и IFTTT
  9. Точный монитор качества воздуха на датчике Sharp GP2Y1010AU0F
  10. Ручной дозиметр (Гейгер-счетчик) на Arduino Nano: сборка в 3D-корпусе