Arduino metal detector?

This my test schema

long t_1 = 0; // ?????????? ??? ???????? ???????? ?????? ????????? ? ???
long t_2 = 0; // ?????????? ??? ???????? ???????? ?????? ????????? ? ???

float L = 0; // ??????????? ???????? ??????? ???????
int A=0; //?????????? ?? ?????????? ?????

void setup()
{
pinMode(13, OUTPUT); // ????????? ?????? 13 ?? ?????
Serial.begin(9600); // ????? ?????????? ? COM-????
}

void loop()
{
// ???? ???????? ???????? ???? ????? ????????
digitalWrite(13, LOW);
while (analogRead (0) != 0 )
{
}

digitalWrite(13, HIGH); // +5? ????? ?? ????? 13
t_1 = micros(); // ?????? ???????? ?? ?????? ?????? ????????? ? ???
A=analogRead (0);

while ( A > 250 ) // ?????? ???? ???????? ????? ?????????? ?? ????? ????? ??? ????
{
A=analogRead (0);
}
t_2 = micros(); // ?????? ???????? ?? ?????? ?????? ????????? ? ???

L = sqrt(float(t_2 - t_1)); // ?????????? ????????????? L.

Serial.println(L); // ????? ??????? ?? ?????
}

G'day guys! Any update on the results of your tests?

I've recently had a play with building a metal detector using an Arduino. First I tried a PI detector inspired by http://www.miymd.com/index.php/projects/tpimd/. That design uses an ATtiny for the receiver, which can do very accurate timing of the decay signal because the ATtiny has a 64MHz fast perhipheral clock. Unfortunately, the Arduino does not have the fast clock, so the timing resolution is 4 times worse. Also, the only mosfets I had available had a Vds rating of 60V, whereas the FQPF7N65C in the original has a breakdown voltage of 650V. So I had to use a shorter pulse and in consequence a lower pulse amplitude. Both of these factors meant that the sensitivity of the detector was poor.

I then tried an induction balance detector. I used timer 1 to generate a 62.5kHz signal, and fed this to timer 2 to generate a 7.8125kHz square wave. I tuned the Tx coil to this frequency using a parallel capacitor, and fed the square wave to the coil/capacitor via a 2.2K resistor, to get a nice 7.8kHz sine wave across the coil. I tuned the receive coil using a another parallel capacitor, and amplified it using an LM358 configured as a bandpass amplifier. Originally I used 2 stages of amplification, but 1 turned out to be sufficient (gain = 50). The output goes into the Arduino ADC, which is set to start a conversion on each cycle of the 62.5kHz signal (I ran the ADC clock at 1MHz and accepted 8-bit accuracy). So the ADC takes 8 readings per cycle, which the code accumulates into 4 bins over around 8000 samples. A little bit of processing improves the sensitivity and cancels the 3rd harmonic, calculates the amplitude measured at 4 points in the cycle, and the phase between the received and transmitted signals.

In summary, the Arduino is used as the transmit signal generator and 4 phase sensitive detectors. This arrangement turned out to be extremely sensitive. As with all induction balance detectors, coil balance is very critical. I also trimmed out the stray capacitance between transmitter and receiver. My original experiments used a double-D coil, but I'm planning to make up concentric coils + bucking coil because I think that will be easier to keep stable.

I summary, using an Arduino as a PI detector requires some extra electronics on the receiving side (maybe a pulse gate and an integrator feeding the ADC would be sufficient); but it makes a good IB detector if you amplify the received signal before passing it to the ADC.

dc42:
I then tried an induction balance detector. I used timer 1 to generate a 62.5kHz signal, and fed this to timer 2 to generate a 7.8125kHz square wave. I tuned the Tx coil to this frequency using a parallel capacitor, and fed the square wave to the coil/capacitor via a 2.2K resistor, to get a nice 7.8kHz sine wave across the coil. I tuned the receive coil using a another parallel capacitor, and amplified it using an LM358 configured as a bandpass amplifier. Originally I used 2 stages of amplification, but 1 turned out to be sufficient (gain = 50). The output goes into the Arduino ADC, which is set to start a conversion on each cycle of the 62.5kHz signal (I ran the ADC clock at 1MHz and accepted 8-bit accuracy). So the ADC takes 8 readings per cycle, which the code accumulates into 4 bins over around 8000 samples. A little bit of processing improves the sensitivity and cancels the 3rd harmonic, calculates the amplitude measured at 4 points in the cycle, and the phase between the received and transmitted signals.

Sounds really awesome :astonished:. I am also interested to build metal detector using arduino but my understanding about microcontroller timing is not so good. Can You share schematic and code?

I would favor a balanced coil metal detector, as you get both amplitude and phase to aid in discrimination what is under the coils. Looks like dc42 has proven the concept.

So, dc42, you are sampling 8 times per cycle, over 1000 cycles? At the same 8 points in the cycle, or are you collecting slightly different times so as to result in 8000 samples per cycle?

Interesting.... this one would be simple to implement in an Arduino:
http://www.docstoc.com/docs/150574517/Matchless-Metal-Locator-(Detector)---By-Thomas-Scarborough-(Silicon-Chip---June-2002)-7S---Geotech

polymorph:
I would favor a balanced coil metal detector, as you get both amplitude and phase to aid in discrimination what is under the coils. Looks like dc42 has proven the concept.

So, dc42, you are sampling 8 times per cycle, over 1000 cycles? At the same 8 points in the cycle, or are you collecting slightly different times so as to result in 8000 samples per cycle?

The same 8 points per cycle. By sampling 4 times per cycle and implementing 2 phase sensitive detectors in software, I can measure both the amplitude and the phase relative to the transmitted signal. Sampling 8 times per cycle allows me to filter out the 3rd harmonic of the transmitted signal as well. This helps because the Arduino generates a square wave to excite the coil, and although the coil is tuned (with a capacitor) to the fundamental, there is still a small amount of 3rd harmonic in the received signal when the coils are well-balanced.

polymorph:
Interesting.... this one would be simple to implement in an Arduino:
http://www.docstoc.com/docs/150574517/Matchless-Metal-Locator-(Detector)---By-Thomas-Scarborough-(Silicon-Chip---June-2002)-7S---Geotech

True, but it doesn't provide any phase information.

Here's the code I am using. It's not complete yet, it doesn't drive the LCD or the earpiece, instead it prints the amplitude and phase to the serial port. I'm now using a MCP6283 op amp instead of the LM358 because it provides better gain at 8kHz. It also has a shutdown pin, which means I shouldn't need an on-off switch as I can put everything to sleep.

// Induction balance metal detector

// We run the CPU at 16MHz and the ADC clock at 1MHz. ADC resolution is reduced to 8 bits at this speed.

// Timer 1 is used to divide the system clock by 256 to produce a 62.5kHz square wave. This is used to drive timer 0 and also to trigger ADC conversions.
// Timer 0 is used to divide the output of timer 1 by 8, giving a 7812.5Hz signal for driving the transmit coil.
// This gives us 16 ADC clock cycles for each ADC conversion (it actually takes 13.5 cycles), and we take 8 samples per cycle of the coil drive voltage.
// Timer 2 is used to generate a tone for the earpiece or headset.

// Wiring:
// Connect digital pin 4 (alias T0) to digital pin 9
// Connect digital pin 5 through resistor to primary coil and tuning capacitor
// Connect output from receive amplifier to analog pin 0. Output of receive amplifier should be biased to about half of the analog reference.
// When using USB power, change analog reference to the 3.3V pin, because there is too much noise on the +5V rail to get good sensitivity.

#define TIMER1_TOP  (255)

// Digital pin definitions
// Digital pin 0 not used, however if we are using the serial port for debugging then it's serial input
const int debugTxPin = 1;        // transmit pin reserved for debugging
const int encoderButtonPin = 2;  // encoder button, also IN0 for waking up from sleep mode
const int earpiecePin = 3;       // earpiece, aka OCR2B for tone generation
const int T0InputPin = 4;
const int coilDrivePin = 5;
const int LcdRsPin = 6;
const int LcdEnPin = 7;
const int LcdPowerPin = 8;       // LCD power and backlight enable
const int T0OutputPin = 9;
const int lcdD0Pin = 10;
const int lcdD1Pin = 11;         // pins 11-13 also used for ICSP
const int LcdD2Pin = 12;
const int LcdD3Pin = 13;

// Analog pin definitions
const int receiverInputPin = 0;
const int encoderAPin = A1;
const int encoderBpin = A2;
// Analog pins 3-5 not used

// Variables used only by the ISR
int16_t bins[4];                 // bins used to accumulate ADC readings, one for each of the 4 phases
uint16_t numSamples = 0;
const uint16_t numSamplesToAverage = 1024;

// Variables used by the ISR and outside it
volatile int16_t averages[4];    // when we've accumulated enough readings in the bins, the ISR copies them to here and starts again
volatile uint16_t ticks = 0;     // system tick counter for timekeeping
volatile bool sampleReady = false;  // indicates that the averages array has been updated

// Variables used only outside the ISR
int16_t calib[4];                // values (set during calibration) that we subtract from the averages

volatile uint8_t lastctr, lastMiss;
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;

void setup()
{
  pinMode(encoderButtonPin, INPUT_PULLUP);  
  digitalWrite(T0OutputPin, LOW);
  pinMode(T0OutputPin, OUTPUT);       // pulse pin from timer 1 used to feed timer 0
  digitalWrite(coilDrivePin, LOW);
  pinMode(coilDrivePin, OUTPUT);      // timer 0 output, square wave to drive transmit coil
  
  cli();
  // Set up timer 1.
  // Prescaler = 1, phase correct PWM mode, TOP = ICR1A
  TCCR1A = (1 << COM1A1) | (1 << WGM11);
  TCCR1B = (1 << WGM12) | (1 << WGM13) | (1 << CS10);    // CTC mode, prescaler = 1
  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;      // clear any pending interrupt
  TIMSK1 = (1 << TOIE1);

  // Set up timer 0
  // Clock source = T0, fast PWM mode, TOP (OCR0A) = 7, PWM output on OC0B
  TIMSK0 = 0;        // disable interrupt (was enabled by Arduino core)
  TIFR0 = 0x07;      // clear any pending interrupt
  TCCR0A = (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
  TCCR0B = (1 << CS00) | (1 << CS01) | (1 << CS02) | (1 << WGM02);
  OCR0A = 7;
  OCR0B = 3;
  TCNT0 = 0;
  
  // Set up ADC to trigger on timer 1 overflow, read channel 0
  ADMUX = /*(1 << REFS0) | */ (1 << ADLAR);    // Use Avcc as voltage reference, read channel 0, left-adjust result
  ADCSRB = (1 << ADTS2) | (1 << ADTS1);
  ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADIE) | (1 << ADPS2);  // enable adc, free running, enable interrupt, prescaler = 16
  DIDR0 = 1;
  sei(); 
  
  Serial.begin(19200);
}

ISR(TIMER1_OVF_vect)
{
  ++ticks;
}

// Interrupt service routine for ADC conversion complete
ISR(ADC_vect) 
{
  int16_t val = (int16_t)(uint16_t)ADCH;    // only need to read most significant 8 bits
  uint8_t ctr = TCNT0;
  if (ctr != ((lastctr + 1) & 7))
  {
    ++misses;
    lastMiss = ctr;
  }
  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;
      memcpy((void*)averages, bins, sizeof(averages));
      memset(bins, 0, sizeof(bins));
      sampleReady = true;
    }
  }
}

void loop()
{
  sampleReady = false;
  while (!sampleReady) {}
  uint16_t oldTicks = ticks;
  
  if (digitalRead(encoderButtonPin) == LOW)
  {
    for (int i = 0; i < 4; ++i)
    {
      calib[i] = averages[i];
    }
    Serial.print("Calibrated: ");
    for (int i = 0; i < 4; ++i)
    {
      Serial.write(' ');
      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;
    
    double amp1 = sqrt((bin0 * bin0) + (bin2 * bin2));
    double amp2 = sqrt((bin1 * bin1) + (bin3 * bin3));
    double ampAverage = (amp1 + amp2)/2.0;
    
    double phase1 = atan2(bin2, bin0) * radiansToDegrees - 45;
    double phase2 = atan2(bin3, bin1) * radiansToDegrees;
  
    if (phase1 > phase2)
    {
      double temp = phase1;
      phase1 = phase2;
      phase2 = temp;
    }
    
    double phaseAverage = (phase1 + phase2)/2.0;
    if (phase2 - phase1 > 180.0)
    { 
      if (phaseAverage < 0.0)
      {
        phaseAverage += 180.0;
      }
      else
      {
        phaseAverage -= 180.0;
      }
    }
                                                          
    //double diff = 
    //Serial.print(ticks);
    //Serial.write(' ');
    //Serial.print(numSamples);
    //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(' ');
    if (phaseAverage >= 0.0) Serial.write(' ');
    Serial.print((int)phaseAverage);
    
    Serial.println();
    //misses = 0;
  }
  while (ticks - oldTicks < 16000)
  {
  }
}

True, but it doesn't provide any phase information.

That's why the Arduino. But it appears that is what you are doing. I was thinking zero crossing for phase information, and you are doing it 10x better by processing it in software.

Thanks! :slight_smile:

I've put the latest code for my metal detector at arduino/MetalDetector at master · dc42/arduino · GitHub because it's now too big to post here. I have made a number of improvements to it. It still outputs to serial monitor (next step is to add an earpiece and LEDs), but it now discriminates between ferrous and non-ferrous metals.

Here is the schematic (it'a also on github). The potentiometer is used to zero the small 90 degrees out-of-phase component of the signal (the in-phase component is nulled by adjusting the relative placement of the coils in typical IB-detector style). If turning the potentiometer up makes the 90 degree component larger instead of smaller, then reverse the phase of one of the coils.

I wound each of the coils using 80 turns of 26swg enamelled copper wire in a D shape, wrapped tape around them, screened them using aluminium foil bound with tinned copper wire (taking care to leave a small gap so that the screen doesn't behave like a shorted turn), and tie-wrapped them on to a plastic plate. See the photo (also on github). The other coil is on the underside. I left long ends on the top coil wires so I can use the spare wire to adjust the coil balance. The plate is 250mm in diameter.

Thanks for sharing! What kind of distances can you detect in air? Is there a possibility to discriminate aluminum foil?

I am working on a pulse-induction type detector with arduino, but it's far from ready.

Regards,
Joop

joop:
Thanks for sharing! What kind of distances can you detect in air? Is there a possibility to discriminate aluminum foil?

When I have the coils well-balanced, I can detect a 22mm copper olive at 200mm. You didn't say what you want to discriminate aluminium foil from, however I find that the phase shift is around -45 degrees for thin aluminium (a foil tray that a mince pie came in last Christmas), compared to around -90 degrees for the copper olive or thick aluminium.

joop:
I am working on a pulse-induction type detector with arduino, but it's far from ready.

I tried using pulse induction too, however I didn't manage to get anything like the same sensitivity. First I tried measuring the decay time to a fixed level using the analog comparator and input capture; then I tried integrating the decay signal and measuring the result using an analog input. The first didn't work well because the analog comparator is too slow and the timing resolution of a 16MHz Arduino is only 62.5ns. The second was more promising, but needs some improvements. It needed a wideband op amp for the integrator to work well with such a short pulse. The gating used to gate the signal to the op amp was critical, because capacitance in the gate was feeding part of the gate signal through to the integrator, adding to the decay signal that I was trying to integrate. Maybe I can get it working better by improving the gating.

When watching "Time Team" series it seems all the treasures are laying up to 200mm down in the UK's soil..

I read somewhere that the depth at which you can detect small objects is typically about the same as the search coil diameter. So if I used a bigger coil, then presumably I would be able to detect objects at greater depths. Is this correct?

So with 6300km coil diameter you can detect the Earth's iron core then? :slight_smile:

A small piece of magnetized steel suspended on a string suffices to detect the Earth's iron core. ;')

I'm thinking of making the construction of an Arduino based metal detector a project for the local maker club. VLF, probably.

pito:
So with 6300km coil diameter you can detect the Earth's iron core then? :slight_smile:

Yes, but you might have some trouble finding enough power to drive the coil. :grin:

polymorph:
I'm thinking of making the construction of an Arduino based metal detector a project for the local maker club. VLF, probably.

What do you think is the best user interface? I've never used a modern commercial metal detector. I'm thinking that it needs some sort of sound in a headset or earpiece, and that sound should convey both how strong the signal is and what its phase is. Also some sort of non-annoying sound the rest of the time to let you know that it is working, such as a short blip once a second.

For the controls, I was thinking of either:

  1. Just a potentiometer to set the detection threshold, a push-button to recalibrate to zero, an on/off switch, and use a fixed phase discrimination threshold.

  2. A rotary encoder with in-built push-button. The push button would be used for calibration (short press), menu selection (medium press) allowing the phase and amplitude thresholds to be adjusted, and on/off (long press for off). The rotary part would be used for amplitude and phase sensitivity adjustment. A 128x64 gLCD would be used both to show the current parameters and a history of what the signal has been for the last few seconds.

I finally started getting good results with pulse induction too. It turned out that I hadn't been putting enough current through the coil. I increased the current from 0.5A to 2A and it has become more sensitive. It took me a while to realize that doubling the current does not double the time taken for the back emf to decay sufficiently to allow the induced field from the target to be measured, but does double the induced field in the target. Since the induced field in the target decays more slowly than the field due to the coil, increasing the current improves sensitivity.

I wanted to keep everything working from a single supply, so currently I'm using a 250V P-channel mosfet to drive the coil. The mosfet is driven from a mosfet gate driver IC. The coil has a 680 ohm damping resistor. A 2K2 resistor feeds the coil voltage to a pair of Schottky clamping diodes to ground, and then to a 100 ohm resistor feeding the op amp inverting input. The op amp is configured as an integrator, with a 2N7000 mosfet connected in parallel with the integration capacitor.

The Arduino uses timer 1 to control everything. I use pin 9 (OC1A) to generate the pulse for the mosfet, and pin 10 (OC1B) to generate the gate signal for the 2N7000, so that the 2N7000 is turned on during the pulse and for a few microseconds afterwards. Then the 2N7000 turns off, allowing the op amp to integrate the signal from the coil. A few microseconds later I use the Arduino ADC to read the integrated voltage.

I had to add an offset nulling circuit, otherwise the input offset voltage of the op amp meant that the integrator output was not stable. I also has to compensate for the capacitive coupling between the 2N7000 gate and the integrator input, by coupling a little of the gate signal into the non-inverting input of the op amp too.

Remaining issues that I want to address are:

  • I could do with even more coil current, maybe 4-5A peak. So I'll probably swap the 250V P-channel mosfet for a 600V N-channel one. This will require using dual supplies, I think, which is unfortunate. Need to choose a mosfet with low output capacitance.

  • Although the op amp I am using (MCP6283) has a gain-bandwidth product of 10MHz, it still isn't fast enough. I have some OPA2320 op amps that I will try next - they have 20MHz GBW and much lower maximum input offset voltage.

  • A coil with lower capacitance would allow a larger damping resistor to be used, resulting in a faster decay time and greater sensitivity. My current coil is wound using 20 turns of ordinary PVC-insulated hookup wire. I might try a spiral-wound coil made from the inner conductor and insulation from coax cable.

I've been reading your posts about the metal detector. Very interesting. I've been thinking about building one as a curiosity, but as my kids lose things in our lawn, I thought it may get them interested in building electronics. Have you made any progress since your last post? I'm interested to see your results.