Welcome to buchanan1.net

J.R. Buchanan


Three potential metal detector circuits

November 2017

Picture of Circuit

The situation

A few months ago, a very good friend wanted to make a metal detector. We decided that I'd either find a circuit on-line, or I'd design our own. While I was doing this, Jeff had a sudden fatal heart attack. Even though I have no need for a metal detector, I'm finishing this in memory of him. In this article, I'll describe three circuits I've tried. When I build a physical unit, that will be the subject of another article.

First Circuit

The first circuit I tried was found on the Instructables web site Here is the link to the article. http://www.instructables.com/id/Simple-Arduino-Metal-Detector/ It's a very clever design that measures the inductance of the search coil using an Arduino microcontroller.

Schematic of Circuit

The Arduino is programmed to send bursts of pulses to the 220 ohm resistor connected to pin A0. Each pulse "charges" the inductor used as the sense coil. When the voltage is applied, the voltage across the inductor rises almost immediately to the applied voltage. No current is passing through the inductor at this point. As time passes, the inductor current rises and the voltage across it drops until it is zero. To prevent damage to the A0 pin of the Arduino, the 220 ohm resistor limits the current to about 23 mA. The width of the pulse is determined by the inductance of the coil, as metal moves near the coil, the inductance and pulse width change. These pulses charge the 0.01 uF capacitor through the diode. The diode prevents the capacitor from discharging back through the coil when the inductor voltage drops. This causes the voltage across the capacitor to rise in a stair-step until the train of pulses is done. The voltage is then measured with the A1 input on the Arduino, then, after measurement, that pin is switched to output and lowered to discharge the capacitor and get ready for the next train of pulses. In the waveforms shown below, the number of pulses sent in each burst of pulses is 7. This may be set in the code uploaded to the Arduino. I've included this code at the end of the article. The version I included is functionally identical to the original Instructables author's code, but re-arranged and with more comments for clarity. The LEDs and the buzzer are connected to 3 of the digital output pins of the Arduino, the LEDs with current-limiting resistors. After some experimentation, I settled on a 3 inch coil with 40 turns. I tried 2 inches and 4 inches, as well as 20 turns. These changes made only minor differences.


When I first built this circuit, it worked, but had problems. As the metal was moved toward the coil, the buzzer went off, and when it moved away, it was silenced. One LED would light for ferrous metals (increasing the inductance of the sense coil), the other would light with non-ferrous metals (lowering the inductance of the sense coil). When metal was held near the coil for 5 seconds the buzzer and light went off, only to return for a few seconds after the metal was removed. This is all by design and is based on the program on the Arduino. So far so good. However the lights and buzzer would keep going off and on at random intervals when no metal was near the coil. This turned out to have two causes. I'd used a ceramic capacitor, and it was not an NPO capacitor. When the temperature of the capacitor changed, the detector would go off. It was so sensitive to temperature that air currents in the room would set it off. I solved this by replacing the ceramic cap with a polypropylene capacitor. That cut the false positives down quite a a bit, you could even blow on the capacitor and not a beep. It still would chirp occasionally when the circuit was idle though. That turned out to be noise and an unstable voltage from the USB port on the computer. It was fairly clean, but not good enough. After that I used the USB cable to upload the program, but when testing it, I used a bench supply set to 9V connected to the barrel plug input to the Arduino. You could use a battery instead. After this the circuit was stable, but there was still a flaw that made this design unusable for my purposes. It would detect larger objects, such a a Coke can, or a pair of diagonal cutters at a reasonable distance. A US quarter however, had to be lowered into the center of the coil to get a reaction. I wanted to be able to detect a coin about 4 inches from the coil, this just would not do.

Second Circuit

The second circuit idea was mine. What if I made the sense coil part of a tank circuit (parallel resonant circuit)? Then I could ring the circuit with a pulse from the Arduino and use the pulseIn() function to measure the width of one positive going half cycle? That would let me know the inductance of the sense coil, but in a different manner than the previous circuit. Would it be better suited for a metal detector?

I searched the web, and, of course, it turns out that it was not an original idea. The author presented the idea as a way to measure inductance using an Arduino, then later in the article he suggested that it might be used as a metal detector. https://reibot.org/2011/07/19/measuring-inductance/

Schematic of Circuit

Again I built the circuit, again it worked the first time. Of course I used polypropylene capacitors and powered from a bench supply. A 5mS pulse is output from the Arduino pin D13 through a 150 ohm resistor to the tank circuit. This causes it to ring, just like striking a bell causes the bell to ring. An LM339 comparator is used to square off the damped sine wave and produce the approximately 0 to 5 volt signal needed by the D12 input to the Arduino. The waveforms below show the damped sine wave and the squared-off pulses sent to the Arduino. If I had a 'scope with more than 2 channels, I'd show the pulse that rings the circuit as well.


Below I've included the code that I wrote to read the resulting pulse width. A quarter had to be lowered into the center of the coil again to get enough of a change in pulse width to be seen (as displayed on a serial monitor hooked to the Arduino). There was no point in writing everything needed to make a metal detector, the sensitivity was about the same as the original circuit. So, once again, close, but not good enough to use as a practical metal detector.

Third, and sucessful, Circuit

While doing this work, I was discussing what I was doing with the author of the original metal detector Instructable. He suggested that the tank circuit could be used in an oscillator, as the inductance of the coil changed, the frequency of the oscillator would change. This is how a traditional BFO (Beat Frequency Oscillator) or "heterodyne" metal detectors works, except, instead of a microcontroller, the traditional circuit uses a second oscillator tuned close to the same frequency with the outputs of both oscillators mixed together to form a beat note that can be heard in a pair of headphones. This produces the familiar squealing noise that many metal detectors make. He suggested as an idea, another Instructable. http://www.instructables.com/id/Home-Made-BFO-metal-detector/

I built one of the oscillators from that Instructable. The circuit used two Colpitts oscillators. I was a little dubious since the circuit seemed overly simplified, especially as far as the DC bias went. Biasing a transistor with a resistor from the power to the base is generally considered bad practice. The circuit can be unstable when the temperature changes. Even worse, if the circuit gets warmer, the collector current increases, which causes more heat, which causes more current, and so on until the transistor overheats. This is called thermal runaway. Still, the circuit worked, it oscillated, and wasn't too sensitive to temperature. There were two other oddities though. The waveforms on both the collector and base were very distorted, they didn't really look like sine waves. Not square either, just badly distorted. The other problem was more serious and made me decide to design my own oscillator circuit. When I doubled the value of the capacitor, the frequency of oscillation (f=1/2pi(LC)^0.5) didn't drop anywhere near the 0.707 of the original frequency that was expected. This scared me away from this circuit.

I decided to design a Colpitts oscillator that used more conventional bias and which would hopefully obey the rules. This circuit uses a 10K and a 4.7K resistor to set the base voltage of the transistor at about 1.6V. With the 1K emitter resistor this sets the quiescent collector current to about 1mA, since the voltage across the emitter resistor is about 1.6V minus the about 0.65V B-E junction drop in the transistor. This causes collector to be at about 3.5V DC, as the 1mA through the 1.5K resistor causes the voltage to be 1.5V lower than the 5V supply. A nice, steady predictable DC operating point. The 0.22uF capacitor from the emitter to ground increases the gain of the circuit which, without this capacitor, would be 1.5K/1K for a gain of 1.5, not enough. The 0.1uF and 0.47uF capacitors and the search coil form the tank circuit. With the junction of the two capacitors grounded, this causes a 180 degree phase shift between the collector and base of the transistor, which is positive feedback, the reason the oscillator works. The 0.01uF capacitor is for DC isolation between the collector and the base. Again, for temperature stability, I used polypropylene capacitors instead of ceramic. It worked and the frequency was proportional to the square root of the capacitor value. The sine wave is still a bit distorted, but not as bad as the other oscillator. I'll include the circuit for the whole metal detector as well as the oscillator below.

Schematic of Circuit

The output of the oscillator is connected to the positive input of one of the comparators, the negative input is connected to a voltage divider that provides about 3.4V, roughly the DC voltage on the collector of the oscillator. As the collector voltage of the transistor rises and falls, the comparator squares off the signal and again produces an about 0-5V swing for pin D2 of the Arduino. The LEDs and buzzer are connected similarly to the first circuit. Again I used a bench supply for clean stable power while testing.


The code for this circuit works by measuring the frequency of the oscillator. It does this by setting pin 2 to trigger a hardware interrupt, once for each cycle of the oscillator. The count continues for 500mS during the line: "delay (DELAY_MS)" The function "meas()" is called once per cycle since it was specified in the setup loop as an interrupt handler. All it does is increment the count. The last 5 measurements are stored in an array and the average is used to compare to the most recent measurement. If there is a difference of 1 count, the buzzer and a light are triggered. If the coil is held near the metal for more than about 2.5 seconds, the reading becomes the new average and the light and buzzer turn off until the coil is pulled away from the metal. Then the light and buzzer are once again triggered until the average count returns to its no-metal state. Thus the sense coil is moved toward the metal and an alert is triggered, then it should be pulled away and the potential metal should be approached from another angle to more accurately locate it.

A listing of the code is below.

This circuit was far more sensitive than the first two, and will be what I use when I build a complete usable metal detector. It will detect a quarter about 4 inches from the coil.There'll be another article posted then.


Upon re-reading the code below, I noticed several misspellings in the comments. I was tempted to correct them, but if I did that, I'd have to re-assemble each circuit on the breadboard and test the code to make sure that I didn't accidentally break it. That may not seem likely, but Murphy's Law. I decided that I didn't want to go to that level of effort, so I've left the misspellings.

First circuit

// Metal detector
// Runs a pulse over the search loop in series with resistor
// Voltage over search loop spikes
// Through a diode this charges a capacitor
// Value of capacitor after series of pulses is read by ADC

// Metal objects near search loop change inductance.
// ADC reading depends on inductance.
// changes wrt long-running mean are indicated by LEDs
// LED1 indicates rise in inductance
// LED2 indicates fall in inductance
// the flash rate indicates how large the difference is

// wiring:
// 220Ohm resistor on D2
// 10-loop D=10cm seach loop between ground and resistor
// diode (-) on pin A0 and (+) on loop-resistor connection
// 10nF capacitor between A0 and ground
// LED1 in series with 220Ohm resistor on pin 8
// LED2 in series with 220Ohm resistor on pin 9

// First time, run with with serial print on and tune value of npulse
// to get capacitor reading between 200 and 300

const byte npulse = 7; // Number of pulses sent to the circuit each time
const bool sound = true;
const bool debug = false;

const byte pin_pulse = A0; // pin that will send pulse to coil
const byte pin_cap = A1;   // pin that will read cap voltage and reset it
const byte pin_LED1 = 12;  // pin to blink LED1
const byte pin_LED2 = 11;  // pin to blink LED2
const byte pin_tone = 10;  // pin connected to buzzer/earphones

const int nmeas = 256;             // number of measurements to take
long int sumsum = 0;               //running sum of 64 sums 
long int skip = 0;                 //number of skipped sums
long int diff = 0;                 //difference between sum and avgsum
long int flash_period = 0;         //period (in ms) 
long unsigned int prev_flash = 0;  //time stamp of previous flash

void setup()
     /* If we're debugging, set up serial port at 9600 baud */
     if (debug) Serial.begin(9600);

     /* set up pulse pint, will send pulses to the coil */
     pinMode(pin_pulse, OUTPUT); 
     digitalWrite(pin_pulse, LOW);

     /* set up pin that will read capacitor coltage, then short it to
        ground when done */
     pinMode(pin_cap, INPUT);

     /* set up pins that will blink the lights and make beeps */
     pinMode(pin_LED1, OUTPUT);
     digitalWrite(pin_LED1, LOW);
     pinMode(pin_LED2, OUTPUT);
     digitalWrite(pin_LED2, LOW);
     if(sound)pinMode(pin_tone, OUTPUT);
     if(sound)digitalWrite(pin_tone, LOW);

     /* set ADC converter refernce to 1.1V instead of 5V */
     // analogReference(INTERNAL);

void loop()
     int minval=1023;
     int maxval=0;
     long unsigned int sum;
     int imeas;
     int ipulse;
     int val;
     long unsigned int timestamp;
     byte ledstat;
     long int avgsum;

     /* loop and count the capacitor voltage */
     sum = 0;
     for (imeas = 0; imeas < nmeas + 2; imeas++) /* why the 2? */
         /* reset the capacitor and set cap pin back to INPUT */
         pinMode (pin_cap, OUTPUT);
         digitalWrite (pin_cap, LOW);
         delayMicroseconds (20);
         pinMode (pin_cap, INPUT);

         /* apply pulses to capacitor, npulse is set above,
            varied to get desired cap voltage */
         for (ipulse = 0; ipulse < npulse; ipulse++)
             digitalWrite (pin_pulse, HIGH); /* takes 3.5 microseconds */
             delayMicroseconds (3);
             digitalWrite (pin_pulse, LOW);  /* takes 3.5 microseconds */
             delayMicroseconds (3);
         /* read the charge on the capacitor */
         val = analogRead (pin_cap); /* takes 13x8=104 microseconds */
         minval = min (val, minval);
         maxval = max (val, maxval);
         sum += val;
         /* determine if LEDs should be on or off */
         timestamp = millis();
         if (timestamp < prev_flash + 10)
            if (diff > 0) ledstat = 1;
            if (diff < 0) ledstat = 2;

         if (timestamp > prev_flash + flash_period)
            if (diff > 0) ledstat = 1;
            if (diff < 0) ledstat = 2;
            prev_flash = timestamp;   
         if ( flash_period > 1000) ledstat = 0;

         /* switch the LEDs to this setting */
         if (ledstat == 0)
            digitalWrite (pin_LED1, LOW);
            digitalWrite (pin_LED2, LOW);
            if (sound) noTone (pin_tone);

         if (ledstat == 1)
            digitalWrite (pin_LED1, HIGH);
            digitalWrite (pin_LED2, LOW);
            if (sound) tone (pin_tone, 2000);
         if (ledstat == 2)
            digitalWrite (pin_LED1, LOW);
            digitalWrite (pin_LED2, HIGH);
            if (sound) tone (pin_tone, 500);
          }    /* end of loop and count the capacitor voltage */

     /* subtract minimum and maximum value to remove spikes */
     sum -= minval;
     sum -= maxval;
     /* process */
     if (sumsum == 0) sumsum = sum << 6; /* set sumsum to expected value
                                            mult sum by 64 */
     avgsum = (sumsum + 32) >> 6;        /* add 32 and divide by 64
                                            does this make sense?
                                            is the 32 shifted right off? */
     diff = sum - avgsum;

     /* adjust for small changes */
     if (abs (diff) < avgsum >> 10)  /* < avgsum divided by 1024 */
        sumsum = sumsum + sum - avgsum;
        skip = 0;
     if (skip > 64)     /* break off in case of prolonged skipping */
        sumsum = sum << 6;

     /* one per mille change = 2 ticks/s */
     if (diff == 0)
        flash_period = 1000000;
        flash_period = avgsum / (2 * abs (diff));
    if (debug)
       Serial.print (nmeas); 
       Serial.print (" ");
       Serial.print (minval); 
       Serial.print (" ");
       Serial.print (maxval); 
       Serial.print (" ");
       Serial.print (sum); 
       Serial.print (" ");
       Serial.print (avgsum); 
       Serial.print (" ");
       Serial.print (diff); 
       Serial.print (" ");
       Serial.print (flash_period);
       Serial.println ();

Second circuit


Written to test the idea of using a tank circuit and an Arduino
as a metal detector


const byte pin_pulse = 13; // pin that will send pulse to coil
const byte pin_in = 12;    // read squared off pulses in

void setup()
  // put your setup code here, to run once:

  Serial.begin (115200);
  /* set up pulse pin, will send pulses to the coil */
  pinMode(pin_pulse, OUTPUT); 
  digitalWrite(pin_pulse, LOW);

  /* set up pin to read pulses in */
  pinMode (pin_in, INPUT);

  Serial.println ("Starting:");
  delay (500);

void loop()
  // put your main code here, to run repeatedly:

  double pulse;

  digitalWrite (pin_pulse, HIGH); /* 5mS pulse */
  delay (5);
  digitalWrite (pin_pulse, LOW);

  delayMicroseconds (56); /* delay until we're sure we're looking at clear ringing, catch second pulse */

  pulse = pulseIn (pin_in, HIGH, 5000);

  if (pulse > 0.1)
    Serial.print ("High for: ");
    Serial.print (pulse);
    Serial.print ("uS\n");
  delay (20); /* Delay and do it all over again */

Third circuit

// store tone as freq

// weird, if two or more Serial.print (var) are called prog works
// normally if one or fewer is called, weird shit happenes
// A problem with interrupts being used by Serial.print() ?
// A stack/heap crash?

#define HIGH_PITCH 2000
#define LOW_PITCH 750
#define DELAY_MS 500      // Period that pulses are counted for in mS
#define ARRAY_SIZE 5      // Size of array used to find average freq
#define POSITIVE_RANGE 1  // How far above avg unit triggers at
#define NEGATIVE_RANGE -1 // How far below avg unit triggers at
#define INTERRUPT_PIN 2   // The pin that the output of the comparator
                          // hooks to, has to be 2 or 3 for Uno
#define LED_1 11          // LED on pin 11
#define LED_2 12          // LED on pin 12
#define BUZZER 13         // piezo buzzer
//#define DEBUG             // when turned on, debug code is activated

volatile long int count;   // volatile since it's changed in another
                           // scope, the interrupt service routine
int ktr = 0;               // declare here, it's incremented in loop()

// declare subs
long int find_avg (long int *values);
void meas (void);
void blink_led_1 (void);
void blink_led_2 (void);
void play_tone (int tone);
void setup() {
  // put your setup code here, to run once:

#ifdef DEBUG
  Serial.begin (9600);

  // Set up pins
  pinMode (LED_1, OUTPUT);
  pinMode (LED_2, OUTPUT);
  pinMode (BUZZER, OUTPUT);
  // Set up interrupt
  attachInterrupt (digitalPinToInterrupt (INTERRUPT_PIN),
                   meas, FALLING);


void loop() {
  // put your main code here, to run repeatedly:
  long int freq;
  long int avg;
  long int values[ARRAY_SIZE];
  count = 0;
  delay (DELAY_MS);
  freq = count;

#ifdef DEBUG
  Serial.print ("\nfrequency: ");
  Serial.print (freq);
  Serial.print ("\n");

  // acording to http://gammon.com.au/interrupts turning off and
  // on the interrupts is appropriate
  // to prevent freq from being changed during assignation
  // in practice this doesn't seem to make any different
  noInterrupts ();
  values[ktr] = freq;
  interrupts ();

#ifdef DEBUG
  Serial.print (" values[ktr] ");
  Serial.print (values[ktr]);
  Serial.print (" ktr ");
  Serial.print (ktr);
  Serial.print ("\n");

  // increment ktr, if it goes past the end of the array, reset to 0
  // when freq is assigned to array (above), we keep the last
  // ARRAY_SIZE of values
  if (ktr > ARRAY_SIZE - 1)
    ktr = 0;

  // find_avg() takes the ARRAY_SIZE values in values[], sums them
  // then divides by ARRAY_SIZE
  avg = find_avg (values);
#ifdef DEBUG
  Serial.print (" avg: ");
  Serial.print (avg);
  Serial.print ("\n");

  if (freq < avg + NEGATIVE_RANGE ){
#ifdef DEBUG
    Serial.print ("********** BEEP (DOWN) **********\n");
    blink_led_1 ();
    play_tone (LOW_PITCH);
  if (freq > avg + POSITIVE_RANGE){
#ifdef DEBUG
    Serial.print ("********** BEEP (UP) **********\n");
    blink_led_2 ();
    play_tone (HIGH_PITCH);

// Find the average value of numbers stored int the array
long int find_avg (long int *in_array) {
  int ktr;
  long int total;

  total = 0;
  for (ktr = 0; ktr < ARRAY_SIZE; ktr++) {
    total += in_array[ktr];

  return (total / ARRAY_SIZE);

// Interrupt Service Routine called by hardware interrupt signal on
// INTERRUPT_PIN The pulses read there are from the output of the
// comparature in the electrical circuit. The comparator is driven
// by a colpitts oscilator which uses the sens coil in the tank
// circuit Metal changes the number of pulses per second
// This ISR counts during the delay (DELAY_MS)
void meas (void){

void blink_led_1 (void){
  digitalWrite (LED_1, HIGH);
  delay (100);
  digitalWrite (LED_1, LOW);

void blink_led_2 (void){
  digitalWrite (LED_2, HIGH);
  delay (100);
  digitalWrite (LED_2, LOW);

void play_tone (int tone){
  int ktr;
  float period;

  period = 1 / (float) tone;          // length of one cycle in seconds
  period = period * 1000000;  // convert to microseconds
  period = period / 2;        // half for HIGH half for LOW

#ifdef DEBUG
  Serial.print ("---> period: ");
  Serial.print (period);
  Serial.print ("\n---> tone: ");
  Serial.print (tone);
  Serial.print ("\n");

  for (ktr = 0; ktr < 50; ktr++){
    digitalWrite (BUZZER, HIGH);
    delayMicroseconds (period);
    digitalWrite (BUZZER, LOW);
    delayMicroseconds (period);