Close

Test Run

A project log for Pulse Induction Metal Detector

My take on an Arduino based PIMD

agpcooperagp.cooper 05/22/2021 at 05:410 Comments

Test Run

I assembled the board, made a simple coil and powered up the Nano without the coil power supply. Some edits to the code but otherwise it appears to be working.

Connected the Coil power supply and amazingly it worked. Some tweaks to the exponential filter constants and it detects small coins within half a coil diameter and large coins at about one coil diameter. About as good as similar (honest) detectors on the Internet.

On the Internet I have seen a demonstration of a PIMD discriminating (not responding to) aluminum foil. This is not real, the detector is just rejecting strong and good conducting targets. Easy to do but does not make a good metal detector.

Hers is the setup:

And the Code:

/* Arduino Pulse Induction Metal Detector */

// Pin definitions
#define pulsePin            4                        // For coil MOSFET gates
#define beepPin             5                        // Buzzer pin
#define compPlus            6                        // Plus (signal) comparator input pin
#define compMinus           7                        // Minus (level) comparator input pin
#define ledPin0            A0                        // Log bar LED0
#define ledPin1            A1                        // Log bar LED1
#define ledPin2            A2                        // Log bar LED2
#define ledPin3            A3                        // Log bar LED3
#define ledPin4            A4                        // Log bar LED4
#define ledPin5            A5                        // Log bar LED5

// Fast versions of digitalWrite(Pin,HIGH); and digitalWrite(Pin,LOW); for all pins on UNO/Nano
#define fastHighPin(Pin) if ((Pin)<8) PORTD|=1<<(Pin); else if ((Pin)<14) PORTB|=1<<(Pin)-8; else if ((Pin)<20) PORTC|=1<<(Pin)-14;
#define fastLowPin(Pin) if ((Pin)<8) PORTD&=~(1<<(Pin)); else if ((Pin)<14) PORTB&=~(1<<(Pin)-8); else if ((Pin)<20) PORTC&=~(1<<(Pin)-14);

volatile unsigned int Magic0=0;
volatile unsigned int Phase0=0;
ISR(TIMER0_COMPB_vect) {
  Phase0+=Magic0;
  if (Phase0<0x8000) {
    fastLowPin(beepPin);
  } else {
    fastHighPin(beepPin);
  }
}

void setup() {
  // Set up pulse pin first - Note: Electronics logic is inverted
  digitalWrite(pulsePin,HIGH);
  pinMode(pulsePin,OUTPUT);
  // Set up comparator inuts
  pinMode(compPlus,INPUT);
  pinMode(compMinus,INPUT);
  // Set up Beeper
  pinMode(beepPin,OUTPUT);
  digitalWrite(beepPin,LOW);
  // Set up Config LED
  pinMode(LED_BUILTIN,OUTPUT);
  // Set up LED bar display 
  pinMode(ledPin0,OUTPUT);
  pinMode(ledPin1,OUTPUT);
  pinMode(ledPin2,OUTPUT);
  pinMode(ledPin3,OUTPUT);
  pinMode(ledPin4,OUTPUT);
  pinMode(ledPin5,OUTPUT);
  // Clear LEDs
  digitalWrite(LED_BUILTIN,LOW);
  digitalWrite(ledPin0,LOW);
  digitalWrite(ledPin1,LOW);
  digitalWrite(ledPin2,LOW);
  digitalWrite(ledPin3,LOW);
  digitalWrite(ledPin4,LOW);
  digitalWrite(ledPin5,LOW);
  
  // No interrupts (just read the ACO bit)
  ACSR=0x00;
                                  
  // Reset timer 1
  TIMSK1=0x00;                                       // Disable all timer interrupts
  TCCR1A=0x00;                                       // Normal (just count up and roll over) Config
  TCCR1B=0x00;                                       // Stop timer

  // Piggy back tone to timer0
  OCR0B=0x80;                                        // Trigger interrupt away from millis() interrupt
  TIMSK0|=(1<<OCIE0B);                               // Enable Timer0 Compare B interrupt

  // Set Serial - for debug
  // delay(100);
  // Serial.begin(9600);
  // while (!Serial);
  // delay(100);
  
}

void loop() {
  // PIMD constants

  // Repetition rate
  unsigned long previousMillis=0;
  unsigned long currentMillis;
  unsigned long intervalMillis=20;

  // Coil pulse timing
  unsigned long pulseTime=500;                       // Coil on time (us)
  unsigned long startMicros;
  unsigned long endMicros;

  // Signal processing
  unsigned long decayTime;                           // The Signal
  unsigned long refExpFilter=0;                      // Reference exponential filter
  unsigned long sigExpFilter=0;                      // Signal exponential filter
  unsigned long refTest;                             // Reference (smoothed) test value
  unsigned long sigTest;                             // Signal (smoothed) test value
  unsigned int refFilterConst=9;                     // Exponential filter time power constant (setting time about 60 seconds)
  unsigned int sigFilterConst=3;                     // Exponential filter response time: lower is faster but more noisy
  unsigned int thresholdConst=1;                     // Sensitivity 

  // Beeper frequency
  unsigned int magicNum=0;                           // magicNum=Freq*67.108864;

  // Stop tone and set beepPin LOW
  Magic0=0;
  Phase0=0;

  // Clear configuration
  bool configState=true;                             
  unsigned int Config=0;                             // Config flag {0|1-6}

  // Get configuration before start
  bool Start=false;                                  // Start flag {false|true}
  
  // Clear Config and bar display LEDs
  digitalWrite(LED_BUILTIN,LOW);
  digitalWrite(ledPin0,LOW);
  digitalWrite(ledPin1,LOW);
  digitalWrite(ledPin2,LOW);
  digitalWrite(ledPin3,LOW);
  digitalWrite(ledPin4,LOW);
  digitalWrite(ledPin5,LOW);

  // Get configuration
  while (configState) {
    // Set Config (A7) before turning on power
    while (analogRead(A7)<=511) {                    // While button pushed
      Config=Config%6;
      Config++;
      digitalWrite(ledPin0,LOW);
      digitalWrite(ledPin1,LOW);
      digitalWrite(ledPin2,LOW);
      digitalWrite(ledPin3,LOW);
      digitalWrite(ledPin4,LOW);
      digitalWrite(ledPin5,LOW);
      if (Config==1) {
        magicNum=16384;                              // 244Hz (freq*67.108864)
        refFilterConst=9;                            // Setting time about 60 seconds
        sigFilterConst=3;                            // Response time (medium)
        thresholdConst=1;                            // Sensitivity (high) 
        digitalWrite(ledPin0,HIGH);
      }
      if (Config==2) {
        magicNum=0;                                  // 0Hz (off)
        refFilterConst=9;                            // Setting time about 60 seconds
        sigFilterConst=3;                            // Response time (medium)
        thresholdConst=1;                            // Sensitivity (high)         
        digitalWrite(ledPin1,HIGH);
      }
      if (Config==3) {
        magicNum=16384;                              // 244 Hz;
        refFilterConst=8;                            // Setting time about 30 seconds
        sigFilterConst=3;                            // Response time (medium)
        thresholdConst=1;                            // Sensitivity (high) 
        digitalWrite(ledPin2,HIGH);
      }
      if (Config==4) {
        magicNum=16384;                              // 244 Hz;
        refFilterConst=7;                            // Setting time about 15 seconds
        sigFilterConst=2;                            // Response time (fast)
        thresholdConst=1;                            // Sensitivity (high) 
        digitalWrite(ledPin3,HIGH);
      }
      if (Config==5) {
        magicNum=0;                                  // 0Hz (off)
        refFilterConst=9;                            // Setting time about 60 seconds
        sigFilterConst=3;                            // Response time (medium)
        thresholdConst=1;                            // Sensitivity (high)         
        digitalWrite(ledPin4,HIGH);
      }
      if (Config==6) {
        magicNum=0;                                  // 0Hz (off)
        refFilterConst=9;                            // Setting time about 60 seconds
        sigFilterConst=3;                            // Response time (medium)
        thresholdConst=1;                            // Sensitivity (high)         
        digitalWrite(ledPin5,HIGH);     
      }
      delay(500);
    }
    // Use power on to exit config
    while (analogRead(A6)<=511) {                    // If exit config state
      while (analogRead(A6)<=511);                   // Wait for buton release
      delay(20);                                     // Wait for debounce
      configState=false;                             // Set exit config state
    }    
  }
                
  Start=true;
  while (Start) {
    // Set up 20ms cycle
    currentMillis=millis();
    if (currentMillis-previousMillis>=intervalMillis) {
      previousMillis=currentMillis;

      // Pulse the coil - Note: Electronics logic is inverted
      fastLowPin(pulsePin);
      delayMicroseconds(pulseTime);
      fastHighPin(pulsePin);
    
      // Measure coil decay time (after coil turned off)
      TCCR1B=0x01;                                   // Start timer 1 (16 MHz clock)
      while (~ACSR&0x20);                            // Wait for comparator to trip
      TCCR1B=0x00;                                   // Stop timer 1
      decayTime=TCNT1;                               // Read timer 1
      TCNT1=0;                                       // Reset timer 1

      // Update reference exponential filter
      refExpFilter=refExpFilter-(refExpFilter>>refFilterConst)+decayTime;

      // Update signal exponential filter
      sigExpFilter=sigExpFilter-(sigExpFilter>>sigFilterConst)+decayTime;

      // Set LED and tone on if signal detected 
      refTest=(refExpFilter>>refFilterConst);
      sigTest=(sigExpFilter>>sigFilterConst);
      if (sigTest>refTest) {
        sigTest=(sigTest-refTest)>>thresholdConst;
      } else {
        sigTest=0;                                   // No signal "reset"
      }
      // Turn off display
      digitalWrite(ledPin0,LOW);
      digitalWrite(ledPin1,LOW);
      digitalWrite(ledPin2,LOW);
      digitalWrite(ledPin3,LOW);
      digitalWrite(ledPin4,LOW);
      digitalWrite(ledPin5,LOW);
      if (sigTest>0) {
        // Generate log bar display
        digitalWrite(ledPin0,HIGH);
        if (sigTest>>=1) digitalWrite(ledPin1,HIGH);
        if (sigTest>>=1) digitalWrite(ledPin2,HIGH);
        if (sigTest>>=1) digitalWrite(ledPin3,HIGH);
        if (sigTest>>=1) digitalWrite(ledPin4,HIGH);
        if (sigTest>>=1) digitalWrite(ledPin5,HIGH);     
        // Start tone
        Magic0=magicNum;                                  
      } else {
        // Stop tone and set beepPin LOW
        Magic0=0;
        Phase0=0;
      }
    }
  
    // Check for power down and restart loop();
    if (analogRead(A6)<=511) {
      while (analogRead(A6)<=511);                   // Wait for buton release
      delay(20);                                     // Wait for debounce
      Start=false;                                   // Set flag off
    }
  }    
}

Receive Signals

Here is the no target receive signal:

It approximates the simulation with the exception of the diodes 1.6v rather than 0.9v.

The signal falls to the threshold voltage (i.e. 40mV) after about 14 us.

Here is a small target response:

The signal falls to the threshold voltage after about 20 us.

And here is a (very) large target response:

The pulse cycle will wait until the signal decays to the threshold, but after 8 ms the results will be meaningless.

Improvements

The first improvement was to lower the threshold voltage to 30mV to increase the sensitivity to small targets. The optimal threshold voltage will depend on the picked up noise level and usability. Its the old problem of detecting a uV signal on top of mV noise.

The main problem that I see is that the exponential smoothing/filtering works but would be better before the analog comparator. Perhaps a higher order low pass filter to remove as much of the no target signal as possible before the analog comparator and the exponential filter post the analog comparator.

AlanX

Discussions