Go Down

Topic: Arduino metal detector? (Read 29187 times) previous topic - next topic

joop

The stuart detectors won't discriminate between different metals. I have built the first version a while back. The Chance detector uses a ATMega8 to investigate the decaying coil voltage. From this we can deduct what type of metal is under the coil. You don't want to dig for iron!

ellisgl


The stuart detectors won't discriminate between different metals. I have built the first version a while back. The Chance detector uses a ATMega8 to investigate the decaying coil voltage. From this we can deduct what type of metal is under the coil. You don't want to dig for iron!


Are you sure about that? Digging up a nice old cannon would be worth it. =)

joop

yeah, but digging for rusted nails isn't fun when you want to find a nice old coin.

ellisgl



This russian project on PIC12F629 controller. Maybe use Arduino ? 

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); // ????? ??????? ?? ?????
}

CephasAntipodes

G'day guys! Any update on the results of your tests?
Any sufficiently advanced technology is indistinguishable from magic.
- Arthur C. Clarke

dc42

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.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

Berimor


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  :smiley-eek:. 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?

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?

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
Steve Greenfield AE7HD
Nick Gammon on multitasking Arduinos:
http://gammon.com.au/blink
http://gammon.com.au/serial
http://gammon.com.au/interrupts

dc42


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.


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.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

dc42

#26
Aug 08, 2013, 06:43 pm Last Edit: Aug 08, 2013, 06:57 pm by dc42 Reason: 1
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.

Code: [Select]

// 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)
 {
 }
}
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

polymorph

Quote
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.
Steve Greenfield AE7HD
Nick Gammon on multitasking Arduinos:
http://gammon.com.au/blink
http://gammon.com.au/serial
http://gammon.com.au/interrupts


dc42

#29
Aug 11, 2013, 11:37 pm Last Edit: Aug 11, 2013, 11:50 pm by dc42 Reason: 1
I've put the latest code for my metal detector at https://github.com/dc42/arduino/tree/master/MetalDetector 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.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

Go Up