News:

;) This forum is the property of Proton software developers

Main Menu

PIC Powered Metal Detector

Started by SeanG_65, Aug 17, 2022, 03:38 AM

Previous topic - Next topic

Wimax

I am slowly progressing a project of my own, but it is a phase-discrimination induction balance type metal detector. Some time ago, using a 'bottom-up' approach  ;D , I bought the coil for a Garret Ace 250 clone model, characterising it and developing the electronics around it. The heart of the project is a PIC24FJ64 that I am now replacing with a PIC24HJ128, keeping the entire circuit intact but taking advantage of the higher clock frequency. Both MCUs provide enough power to handle sensitivity control, processing, signal generation, fast two-channel sampling and the phase discrimination algorithm performed entirely in software, pinpointing, display information and the generation of the detection confirmation audio tone. Being able to work on it in my spare time is slowly growing, but I have already made two preliminary releases and it doesn't look bad.
With a little patience, it would be interesting to integrate an advanced material discrimination logic.

Has anyone experimented with an induction balance type metal detector driven by a PIC & Positron 8 or 16 ?


Yves

Hi Wimax,

I found your project very interesting. Will you like sharing your project.

Yves
Yves

SeanG_65


top204

"Inside the Metal Detector" is an excellent book with good details and circuits, written by a person who knows what he is talking about. It taught me quite a few things about the more recent metal detectors that I did not know previously. I am going to convert his PIC16F C code into Position8 as soon as time permits, and place it here on the forum.

The book can be found online, but if George is still getting commission for the book sales, I will not add a link to it here.

Wimax

Quote from: Yves on Mar 25, 2023, 03:31 PMHi Wimax,

I found your project very interesting. Will you like sharing your project.

Yves


I would first like to finish and test the latest version to avoid surprises  ;)

top204

Your metal detector sounds fascinating Wimax. I am looking forward to seeing the circuit and the code, especially when you are using the phase mechanism with the I and Q values. It is something out of my knowledge bracket for now, until I see how someone who understands the phase mechanism makes it work in code.

Are you also using it for ground capacitance compensation?




Wimax

Hi Les,

Yes, simplifying a lot, in the induction balance mode the principle is exploited according to which, in the absence of targets, thanks to the geometry of the search coil (shape and relative positioning between the TX and RX coils) the received signal is zero or in any case very small. The presence of an object nearby tends to unbalance the field and the eddy currents in the material generate a field which couples with the receiving coil. The amplitude of the received signal as well as the phase difference between the transmitted signal and the one available at the ends of the receiving coil depends on the nature of the material struck by the magnetic field (ferromagnetic, non-ferromagnetic, etc.) and on its dimensions.
It is theoretically possible to create a logic by which to discriminate the nature of objects by examining the phase difference.
In reality, things are a little more complex, but the basic principle remains valid.

In practice, I generate a continuous waveform through the PWM inside the PIC24, I amplify and filter it also using the inductance of the transmission coil and an appropriate capacitance (the structure is resonant).
I take a replica of the signal injected into the coil and send it to an ADC channel.
The signal coming from the receiving coil is filtered and amplified (with a gain selectable from the PIC24) and sent to a second channel of the ADC.
In the heart of the algorithm, the two channels are sampled simultaneousy at a suitable speed such as to obtain an integer number of samples for each period (from 20 to 80 samples per period).
Then I generate a third synthetic signal in quadrature with respect to the one transmitted by delaying the samples of the latter by a quarter of a period. So I have three vectors: In-phase component (I) of the transmitted signal, quadrature component (Q) of the transmitted signal and the received signal.

During calibration (ie with the search coil in the air or on the ground free of metal objects) I calculate the phase differences between I and the received signal and Q and the received signal. This allows to calculate the angle between the TX and RX signal over the four quadrants. This value is stored. The power of the received signal is then calculated and subsequently stored.

Once the calibration is complete, the algorithm goes into the search phase by performing a series of acquisitions, interspersed with short pauses, and the phase shift angle and instantaneous power are calculated for each acquisition. The calculated angles are then corrected using the data obtained during calibration.
Since the search takes place by making the coil move with oscillating movements parallel to the ground, the presence of an object must be linked to the variation of the received signal with a suitable time constant. In fact, the algorithm performs a filtering of the stored data by detecting the amplitude variations between the samples, comparing them with a threshold and observing the phase delta. If there is no signal variation there is no information.
If the detection is successful, the power and phase shift information is written on an LCD display and the generation of an acoustic tone is activated via a second PWM channel.
Having a database available to store in the PIC24 it would also be possible to try to recognize the type of material.
There's a lot of work to do and, unfortunately, free time is always little... I'm progressing slowly. I'm finishing the PCB layout, I hope I can share something useful soon!  ;D

SeanG_65

Wimax, I appreciate that the coils need to be balanced, but you can do it electrically by having a phase and amplitude controlled signal generator channel driving an third coil closely coupled to the reciever coil. You can adjust the drive signal until there is zero output from the Rx coil. The benefit of this is that when there is a large "Ground matrix" signal present in poor soild conditions, you can null it out.

Bear in mind the signals on I & Q are the following (GROUND + target) and (ground + TARGET). An analysis of the signals, and the ratio in which they occur can be done using a matrix equation. The problem with Induction Blalance is that when the ground is very bad (mineralised with magnetite etc which give a NEGATIVE phase response) or in the case of a beach, salt which gives a POSITIVE angle, this causes an imbalance in the coil and results in the Rx coil saturating and a loss in overall sensitivity. If the coil is balanced electronically, it can be kept "on edge" and give better sensitivity regardless of the ground conditions.

Wimax

Yes, consider that I chose to start the adventure from a coil already available and sold as compatible with a well-known, but not very sophisticated model of metal detector (double-D plate with only two windings).
As I said, I proceed like a snail, one version after another. Would it also be interesting to attempt compensation by generating a counterphase component at the receiver level before A/D conversion by having the information obtained during calibration ?

SeanG_65

#29
I don't think that would work as it is the fact that the coil is "balanced on a knife edge" that makes the machine more sensitive. The better the balance, the more sensitive the machine, though if you google "Off-Resonance" metal detector, you will find some interesting concempts you can play with.

Wimax

Very interesting, thank you !
Amazingly I was able to finish the first complete prototype 🤓, obviously in the air it works well, but I will test it as soon as possible in the field.
The end of the summer season will be the best time to test it, I can wait a little bit longer. 😬

JonW

I read about the off-resonance detection, quite a neat concept where you use the shift in resonance and the gradient of the loaded Q slope to provide the error or detection.  Sure I have read about this in a museum on old mine detectors
The article also mentioned that the frequency of the signal was better for certain metals.  Do these detectors have multiple coils or switched cap banks to alter the resonant frequency or use a lower Q wider BW coil to operate over multiple bands?  The NCO's on the newer lower enc PICs can cover hz to 32M now and would be ideal for low-cost multi-band detectors.

Also do modern detectors use multiple detection techniques in one product, such as off-resonance and pulse inductance etc

Wimax

I wonder about the order of magnitude of the variation of the search coil inductance with useful targets. Does anyone know of any commercial detectors using this technique ? Well-founded suspicions are also accepted  ;D

trastikata

Interesting topic, I've decided to educate myself a bit about metal detector techniques and found this to be a good start to cover the basics. Hope it helps.

KBA_METAL_DETECTOR_BASICS_&_THEORY

Bravo

In about 1995, I made a handheld metal detector for checking people. It had a 120 Khz oscillator into a divider chip & into a PIC which was probably a 12C508. when you turned it on, the PIC measured the pulse width & saved it. It then compared the new divided frequency against what was saved, & emitted  tones according to the difference.  It was good enough for purpose. As the battery ran down, the the new turn on reference compensated for the new frequency.  With modern chips running at higher frequencies, I should imagine you could lose the divider chip & make it very sensitive. That frequency  is most suited for steel.
Retired RF Tech

Wimax

#35
Greetings everyone ! ;D

After quite a long time, here I am finally here to share the results of my efforts.
I thought that trying to share the project might be the best way to try to improve it, should it be of interest to the reader.
I wish I could have spent more time on it, after all, especially in the in-field testing phase, but most of my energies were distributed among my spare time to design the PCB, write the firmware, and make/verify the prototype.
In fact, as I anticipated in an old post, I went ahead one step at a time with the relevant PCB to be tested. In fact, I spent not only energy but also some money making SMD prototypes.
The detector is fully digital, excluding analog circuitry to amplify the signal injected and received by the coil.
The detector offers the functions of auto-calibration, sensitivity selection and data transmission via serial port on the "COM" connector (just enter the necessary lines of code and equip yourself with a simple RS232 level converter).
The micro is capable of calculating the phase shift between the injected and received signal without synchronous detectors as well as calculating its power .
In the current version, the detection is performed on a sliding buffer of 3 samples (other firmware versions use different techniques) suitable for sweeping speeds around 1 meter per second.
Upon target detection, engineering data of signal power (dimensionless) and phase shift in degrees between transmitted and received signals are displayed on a WaveShare 2x16 I2C RGB display while a beep is generated with a frequency proportional to the received power.
The micro is a 24HJ128GP202 pushed to 80 MHz while for the coil I used a double-D compatible with the made-in-China MD6350 detector.
The design is, of course, improvable both in terms of hardware and firmware, but for the latter aspect the micro offers quite a bit of room.
I have two identical and assembled prototypes of this version, one has been integrated, the other is an unused twin.

Here there is the firmware rev. 4.2, while the schematic is in attachment.

 ;)

'*****************************************************************************************************************
'*  Name    : MARK V.BAS                                                                                         *
'*  Author  : [Max]                                                                                              *
'*  Notice  : Copyright (c) 2023/2024                                                                            *
'*          : All Rights Reserved                                                                                *
'*  Date    : 25/04/2024                                                                                         *
'*  Version : 4.2                                                                                                *
'*  Notes   : Based on PIC24HJ128GP202 @ 80 MHz - 40 MIPS                                                        *
'*          : simultaneous sampling @ 333.33 ksps                                                                *
'*          : phase shift calculation algorithm verified                                                         *
'*          : MD 6350 clone coil used @ 8.33 KHz                                                                 *
'*          : WaveShare RGB I2C Display                                                                          *
'*          :                                                                                                    *
'*****************************************************************************************************************

Device = 24HJ128GP202

Declare Xtal= 80

Config FBS = BWRP_WRPROTECT_OFF
Config FGS = GWRP_OFF
Config FOSCSEL = FNOSC_PRIPLL, IESO_OFF
Config FOSC = FCKSM_CSDCMD, IOL1WAY_OFF, OSCIOFNC_OFF, POSCMD_XT
Config FWDT = WDTPOST_PS256, WINDIS_OFF, FWDTEN_OFF
Config FPOR = FPWRT_PWR128, ALTI2C_OFF
Config FICD = ICS_PGD1, JTAGEN_OFF

PLL_Setup(40, 2, 2, $0300)    ' 80 MHz

Dim wAdcResult1[16] As Word DMA     ' Define 16 word buffer in DMA memory
Dim wAdcResult2[16] As Word DMA     ' Define 16 word buffer in DMA memory

Dim appo1 As Word
Dim appo2 As Word

Dim dwAddressOfRamArray1 As Dword
Dim dwAddressOfRamArray2 As Dword


Dim a As Word
Dim b As Word
Dim c As Byte
Dim d As Byte
Dim dma_numcampioni As Byte
Dim k As Byte
Dim FL As Byte
Dim XX[530] As Word  ' Samples @ AN1
Dim YY[530] As Word  ' Samples @  AN0
Dim ZZ[530] As Word  ' 90° Delayed samples
Dim SW[530] As Word  ' Swap buffer

Dim Address As Byte
Dim Rs As Bit
Dim Resistenza As Byte
Dim Testo As String *17
Dim N As Word

Dim KK As Byte
Dim TM As Byte
Dim NA As Word
Dim M As Dword
Dim FREQ As Word
Dim Bat As Word

Dim CHA[8] As Word
Dim CHB[8] As Word
Dim PHOut As Float
Dim PHOut1 As Float
Dim PHOut2 As Float
Dim N_Periodi As Word
Dim FLV As Bit
Dim M1 As Dword
Dim M2 As Dword
Dim Picco[3] As Float
Dim Fase_X[3] As Float
Dim Derivata As Float
Dim P_media As Float
Dim A1 As Dword
Dim A2 As Dword
Dim PH0 As Float
Dim XO As Float
Dim Is As Float
Dim Qs As Float
Dim Soglia As Float
Dim Suono As Byte
Dim MMAX As Float
Dim Indice As Byte
Dim Display As Byte
Dim Detection As Bit
Dim BtnVar As Byte
Dim BtnVar1 As Byte
Dim Sens As Byte

Dim Carry As Bit

Declare Hserial_Baud = 38400 ' USART1 Baud rate

Declare I2C_Slow_Bus 1  ' Reduced clock for I2C communications
Symbol LCD_ADD = (0x7c) ' Target LCD
Symbol RGB_ADD = (0xc0) ' Target RGB
Symbol TPL_WRT = (0x5c) ' Target Digital Potentimeter
Symbol TPL_RD =  (0x5d) ' Target Digital Potentimeter
Symbol SDA_Pin = PORTB.2 ' Alias the SDA (Data) pin
Symbol SCL_Pin = PORTB.6 ' Alias the SCL (Clock) pin

Symbol LED = PORTB.9
Symbol FreqPin = PORTB.10

Declare HRSOut1_Pin = PORTB.12 ' Pin to be used for TX with USART1
Declare HRSIn1_Pin = PORTB.13  ' Pin To be used For RX with USART1
PPS_Output(cOut_Pin_RP12, cOut_Fn_U1TX) ' Map UART1 TX pin to RP12
PPS_Input(cIn_Pin_RP13, cIn_Fn_U1RX) ' Map UART1 RX pin to RP13
 
dma_numcampioni=16    ' Samples to be transferred via DMA
 
TRISA.1 = 1      ' AN1 as input port
TRISA.0 = 1      ' AN0 As Input port
TRISB.15 = 1     ' AN9 As Input port for battery test

Include "PWM_33_Max.inc"  ' Load the Hardware PWM procedures into the program
Include "ADin.inc"

Declare CCP1_Pin PORTB.7
Declare CCP2_Pin PORTB.10

PPS_Output(cOut_Pin_RP7, cOut_Fn_OC1)   ' Make RP7(RB7) the pin for    PWM1
PPS_Output(cOut_Pin_RP10,cOut_Fn_OC2)   ' Make RP10(RB10) the pin for  PWM2

' Set unused port to GND

PinLow PORTA.4
PinLow PORTB.3
PinLow PORTB.4
PinLow PORTB.11
PinLow PORTB.14


For a = 0 To 15
        wAdcResult1[a]=0   ' DMA buffer clear
        wAdcResult2[a]=0
Next

DelayMS 200
I2C_LCD_Init()    ' Inizializzo il Display LCD
DelayMS 100
I2C_RGB_Init()    ' Inizializzo il Controller RGB
DelayMS 100

I2C_RGB_Colour (0,255,0)   ' Colori

DelayMS 100

I2C_LCD_Clear()   ' Clear LCD Display
DelayMS 100

I2C_LCD_XY(0,0) ' Cursor @ upper left position
I2C_LCD_Write ("    Digital    ")
I2C_LCD_XY(0,1)
I2C_LCD_Write (" Metal Detector ")
DelayMS 1500
I2C_LCD_Clear()
I2C_LCD_XY(0,0)
I2C_LCD_Write ("     MARK 5    ")
I2C_LCD_XY(0,1)
I2C_LCD_Write ("(c) 2024 by MAX ")
DelayMS 2000
I2C_LCD_Clear()
I2C_LCD_XY(0,0)
I2C_LCD_Write ("    FW REV 4.2  ")

DelayMS 2000
I2C_LCD_Clear()

' Load battery patterns in CGRAM
I2COut SDA_Pin, SCL_Pin, LCD_ADD,[$080,%01000000]
DelayMS 50
I2COut SDA_Pin, SCL_Pin, LCD_ADD,[$040,0xe,0x1b,0x11,0x11,0x11,0x11,0x11,0x1f,0xe,0x1b,0x11,0x11,0x11,0x11,0x11,0x1f,0xe,0x11,0x11,0x11,0x11,0x1f,0x1f,0x1f,0xe,0x11,0x11,0x11,0x1f,0x1f,0x1f,0x1f,0xe,0x11,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0xe,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f]
DelayMS 50

I2C_RGB_Colour (255, 0, 255)   
I2C_LCD_XY(0,0)
I2C_LCD_Write ("  Wait Please  ")
I2C_LCD_XY(0,1)
I2C_LCD_Write ("  -----------  ")

DelayMS 1500

ADC_Configure ()     ' ADC Configuration
DMA_Config()         ' DMA Configuration
DelayMS 150
FL=0
M=0
N=0
M1=0
TM=0
PH0=0
N_Periodi=449
FREQ=8250 ' f_sampling/FREQ=~20 in order to generate a satisfactory 90° shift
Sens=90  ' Default sensitivity
Write_Trimmer(Sens)' The lower the value, the lower the gain

HPWM1_SetFreq(FREQ,127)

If DMACS1bits_PPST1=1 Then     ' Check selected ping-pong buffer

   FLV=0

   Else

   FLV=1

EndIf
FL=0

' Calls calibration routine
Calibrazione()

' Set Timer1 @ 1 second
TMR1 = 0
T1CON = %1000000000110000 ' Start Timer1
PR1 = 40000 ' Load Timer1 period
IFS0bits_T1IF = 0 ' Clear Timer1 interrupt flag
IPC0bits_T1IP0 = 1 ' Set priority  a 1
IEC0bits_T1IE = 1 ' Enable the Timer1 interrupt

' Set Timer3 @ 3 ms. Each tick triggers a group of samples acquisition

TMR3 = 0
T3CON = %1000000000110000 ' Start Timer1
PR3 = 500 ' Load Timer1 period @ 3 ms

IFS0bits_T3IF  = 0 ' Clear Timer3 interrupt flag
IPC2bits_T3IP0 = 0 ' Set priority
IPC2bits_T3IP1 = 1 ' Priority 2
IEC0bits_T3IE  = 1 ' Enable the Timer3 interrupt

INTCON1bits_NSTDIS=0

IPC3bits_DMA1IP0 =1 '  Priority 3
IPC3bits_DMA1IP1 =1 '  Priority 3

Carry=0 ' Used to check the difference buffer filling mode
Display=0 ' Linked to the timer for clearing the Display after target discovery
Detection=1 ' Set to make the Display switch off the first time after initialisation
BtnVar=0
BtnVar1=0
Rs=0
k=0
XO=0
' Clear the difference buffer

Picco[0]=0
Picco[1]=0
Picco[2]=0
Fase_X[0]=0
Fase_X[1]=0
Fase_X[2]=0


Main:

' Check buttons for calibration and sensitivity, disable debouncing
'

Button PORTB.8, 0, 0, 0, BtnVar, 1, Cali    '   if CAL button is pressed then calls calibration routine
Button PORTB.5, 0, 0, 0, BtnVar1, 1, Senso  '   if SENS button is pressed then calls the sensitivity change routine (step 10)

If Display=4 And Detection=1 Then      ' Detection gone, switch-off the tone

    HPWM2_SetFreq(FREQ,0)

EndIf

If Display=10 And Detection=1 Then      ' Detection gone, time elapsed, switch-off the display

    Display=0
    Detection=0
    I2C_RGB_Colour (0, 0, 0)   

EndIf


GoTo Main

Cali:

    IEC0bits_T1IE = 0 ' Disable Timer1 interrupt
    IEC0bits_T3IE = 0 ' Disable Timer3 interrupt
   
    I2C_RGB_Colour (255, 0, 255)
    Calibrazione()
    Detection=1
    Display=0
    IEC0bits_T1IE = 1 ' Enable  Timer1 interrupt
    IEC0bits_T3IE = 1 ' Enable  Timer3 interrupt


GoTo Main

Senso:
    IEC0bits_T1IE = 0 ' Disabled Timer1 interrupt
    IEC0bits_T3IE = 0 ' Disable  Timer3 interrupt
    I2C_LCD_Clear()
    Detection=1
    Display=0
    I2C_RGB_Colour (255, 0, 255)
    Dec Sens,10
    If Sens=0 Then
        Sens=90
    EndIf
    Write_Trimmer(Sens)
    I2C_LCD_XY(0,0)
    Testo="Sens. "+Str$(Dec Sens)+"  "
    I2C_LCD_Write (Testo)
    DelayMS 1500
    ' Sensitivity changed, a new calibration needed
    Calibrazione()
    I2C_LCD_Clear()
    I2C_LCD_XY(0,0)
    I2C_LCD_Write (" Search Active  ")
    I2C_LCD_XY(0,1)
    I2C_LCD_Write ("   ----------   ")
    IEC0bits_T3IE = 1 ' Enable Timer3 interrupt
    IEC0bits_T1IE = 1 ' Enable Timer1 interrupt
GoTo Main

   Isr DMA1Interrupt   ' Acquire 8+8 samples block via DMA 
               a=0
               Repeat
                   b=a+KK*8 ' Calculate the address for vectors X and Y
                   c=a*2
                   d=c+1

                   If FLV=0 Then    ' Use the first buffer while the other is being filled
                       appo1=wAdcResult1[c]
                       appo2=wAdcResult1[d]
                       Else
                       appo1=wAdcResult2[c]
                       appo2=wAdcResult2[d]
                   EndIf

                   YY[b]=appo1     
                   XX[b]=appo2
                   Inc a,1
               Until a=8
               Inc KK,1   ' Increase the counter, at every step I have 8 samples from channel AN0 and 8 samples from channel AN1

         FLV=~FLV    ' Bit toggle for Ping-Pong buffer selection
       
       
         IFS0bits_DMA1IF = 0; //Clear the DMA1 Interrupt Flag


   EndIsr


   Isr T1Interrupt ' Timer1 is used to manage the display and read the battery status as well as to blink the status LED

       Inc TM,1
       Inc Display,1
       Toggle PORTB.9
   
       IFS0bits_T1IF = 0 ' Reset the Timer1 interrupt flag
   EndIsr ' Context restore and exit the interrupt

' Handle a Stack exception

ISR_Start StackError
    HRSOut "Stack error occured\r"
    INTCON1bits_STKERR = 0         ' Clear the trap flag
ISR_End

Isr T3Interrupt ' Timer3 is set to generate an Interrupt every 3ms triggering the acquisition of 512 samples on the two parallel channels

                KK=0
               
                IEC0bits_DMA1IE  = 1; //Set DMA interrupt
                While KK <=64:Wend   ' Acquire FL*8 samples for each channel
                IEC0bits_DMA1IE  = 0; //Disables DMA interrupt 

                Conditioning()

' Phase difference estimation routine on acquired samples
' PH0 is the angle between TX and RX estimated during calibration, I rotate the RX axis of coordinates (Cos[PH0], Sin[PH0]) by an angle PH0 in a counterclockwise direction
' I calculate the components of the received vector with respect to the new rotated axis to cancel the angular offset


                Is=(Cos(PHOut)*Cos(PH0))+(Sin(PHOut)*Sin(PH0))
                Qs=(Sin(PHOut)*Cos(PH0))-(Cos(PHOut)*Sin(PH0))
   
                If k>2 Then ' I have acquired a triplet of samples, from now on I shift the buffer by 1 position and insert the new sample to the right
               
                             k=0
                             Carry=1
                EndIf
               
                If Carry=0 Then
               
                Picco[k]=P_media-Soglia ' Calculation of the signal's peak power relative to the threshold calculated in Calibration
                Fase_X[k]=(180/3.14)*ATan2(Qs,Is) ' I calculate the phase shift between RX and TX via ATan2 which resolves the angle on the 4 quadrants
                Inc k,1
               
                    Else
                    ' Shift the buffers by 1 position
                      Picco[0]=Picco[1]
                      Picco[1]=Picco[2]
                      Picco[2]=P_media-Soglia
                      Fase_X[0]=Fase_X[1]
                      Fase_X[1]=Fase_X[2]
                      Fase_X[2]=(180/3.14)*ATan2(Qs,Is)
                EndIf
'---------------------------------------------------------
' ---------------------------------------------------------
' Simple detection algorithm based on 3 samples differences
' a suitable scan speed Is around 1 meter/s
' ---------------------------------------------------------
' ---------------------------------------------------------
       
                             If (Picco[1]-Picco[0])> 3 And (Picco[1]-Picco[2])> 3  Then    'Central sample is MAX, the treshold "3" seems to be good enough
                           
                                IEC0bits_T1IE = 0 ' Disable the Timer1 interrupt
                                MMAX=Picco[1]
                                Detection=1 ' Detection performed
                                Display=0
                                I2C_RGB_Colour (255, 0, 255)
                                I2C_LCD_XY(0,0)
                                Testo="Power "+Str$(Dec1 MMAX)+"      "
                                Testo=Left$(Testo,16)
                                I2C_LCD_Write (Testo)
                                I2C_LCD_XY(0,1)
                                Testo="Phase "+Str$(Dec1 Fase_X[1])+"     "
                                Testo=Left$(Testo,16)
                                I2C_LCD_Write (Testo)
                                Disp_Bat()
                                FREQ=1500+(1500*MMAX)/724  ' Calculates a sound frequency proportional to received signal strength
                               
                                HPWM2_SetFreq(FREQ,127) ' Plays a non-blocking sound
                                IEC0bits_T1IE = 1 ' Enable the Timer1 interrupt
                            EndIf
               
IFS0bits_T3IF = 0 ' Reset the Timer3 interrupt flag
EndIsr ' Context restore and exit the interrupt


End



Proc ADC_Configure()

    AD1PCFGLbits_PCFG1 =0     ' Input on AN1
    AD1PCFGLbits_PCFG0 =0     ' Input on AN0
   
    AD1CON1bits_ADON = 0' Turn Off the A/D converter
    AD1CON1bits_ADSIDL = 1'  Stop conversions in SLEEP mode
    AD1CON1bits_ADDMABM = 1' Write sequentially
    AD1CON1bits_AD12B = 0'   10 Bit resolution
    AD1CON1bits_FORM1 = 0' Unsigned integer mode
    AD1CON1bits_FORM0 = 0' Unsigned integer mode
    AD1CON1bits_SSRC2 = 1'   Internal clock as trigger
    AD1CON1bits_SSRC1 = 1'   Internal clock as trigger
    AD1CON1bits_SSRC0 = 1'   Internal clock as trigger
    AD1CON1bits_SIMSAM = 1' Simultaneous sampling
    AD1CON1bits_ASAM = 1'   Autosampling on
    AD1CON2bits_VCFG2 = 0'  Use Internal reference (AVdd, AVss)
    AD1CON2bits_VCFG1 = 0'
    AD1CON2bits_VCFG0 = 0'
    AD1CON2bits_CSCNA = 0'  Do not scan multiple inputs
    AD1CON2bits_CHPS1 = 0' Sample CHO & CH1
    AD1CON2bits_CHPS0 = 1' Sample CHO & CH1
    AD1CON2bits_SMPI3 = 0'
    AD1CON2bits_SMPI2 = 0'
    AD1CON2bits_SMPI1 = 0'
    AD1CON2bits_SMPI0 = 1' Increments the DMA address after completion of every 2nd sample/conversion operation
    AD1CON2bits_BUFM = 0'   Always starts filling buffer at address 0x0
    AD1CON2bits_ALTS = 0'   Alternate mode OFF
    AD1CON3bits_ADRC = 0'   Use system clock
    AD1CON3bits_SAMC0=0
    AD1CON3bits_SAMC1=1  ' Auto Sample Time = 6 * TAD
    AD1CON3bits_SAMC2=1
    AD1CON3bits_SAMC3=0
    AD1CON3bits_SAMC4=0
    AD1CON3bits_ADCS0=1  ' ADC @ 333.3 Ksps  - 2 channels with simultaneous sampling -
    AD1CON3bits_ADCS1=1
    AD1CON3bits_ADCS2=0
    AD1CON3bits_ADCS3=0
    AD1CON3bits_ADCS4=0
    AD1CON3bits_ADCS5=0
    AD1CON3bits_ADCS6=0
    AD1CON3bits_ADCS7=0
    AD1CON4bits_DMABL2 = 1' Allocates 16 words of buffer to each analog input
    AD1CON4bits_DMABL1 = 0'
    AD1CON4bits_DMABL0 = 0'
    AD1CHS0bits_CH0SA4 = 0' CHO MUX A, Positive input is AN1
    AD1CHS0bits_CH0SA3 = 0'
    AD1CHS0bits_CH0SA2 = 0'
    AD1CHS0bits_CH0SA1 = 0'
    AD1CHS0bits_CH0SA0 = 1'
    AD1CHS0bits_CH0NA = 0'   CHO MUX A, Negative Input is Vref- (AVss)
    AD1CHS123bits_CH123SA=0   ' CH1 select input AN0
    AD1CHS123bits_CH123NA0=0  ' Negative Input is Vref- (AVss)
    AD1CHS123bits_CH123NA1=0  ' Negative Input is Vref- (AVss)
    IFS0bits_AD1IF = 0; // Clear the A/D interrupt flag bit
    IEC0bits_AD1IE = 0; // Do Not Enable A/D interrupt
    AD1CON1bits_ADON = 1' Turn on the A/D converter


EndProc


Proc DMA_Config()

    DMA1CONbits_CHEN=0

    dwAddressOfRamArray1 = AddressOf(wAdcResult1)
    dwAddressOfRamArray2 = AddressOf(wAdcResult2)

    DMA1CONbits_SIZE = 0;    // Transfer data as Word (2 bytes)
    DMA1CONbits_DIR = 0;     // Transfer data from ADC to RAM
    DMA1CONbits_HALF = 0;    // Trigger the interrupt when ALL samples have been transferred
    DMA1CONbits_NULLW = 0;   

    DMA1CONbits_AMODE1 = 0;  // Transfer data using the address in DMA1STA and autoinc
    DMA1CONbits_AMODE0 = 0; 

    DMA1CONbits_MODE1_DMA1CON  = 1;   // Continuos Ping-Pong mode
    DMA1CONbits_MODE0_DMA1CON  = 0;   // Continuos Ping-Pong mode

    DMA1REQbits_IRQSEL0=1
    DMA1REQbits_IRQSEL1=0
    DMA1REQbits_IRQSEL2=1
    DMA1REQbits_IRQSEL3=1
    DMA1REQbits_IRQSEL4=0
    DMA1REQbits_IRQSEL5=0
    DMA1REQbits_IRQSEL6=0
    DMA1REQbits_FORCE= 0
     
    DMA1STA = dwAddressOfRamArray1
    DMA1STB = dwAddressOfRamArray2

    DMA1PAD=0x0300 ; Physical address of ADC1BUF0 register for data fetch
    DMA1CNT = dma_numcampioni-1 ; The number of transfers to be made before triggering the interrupt and restarting from the first location is equal to the buffer size

    IFS0bits_DMA1IF = 0; //Clear the DMA interrupt flag bit
    IEC0bits_DMA1IE  = 0; //Disable the DMA interrupt enable bit
    DMA1CONbits_CHEN=1  ; //Set the DMA Channel 1

EndProc


Proc Conditioning(),Float

        ' XX received signal
        ' YY transmitted signal
        ' I delete the first 8 samples to avoid initial dirty buffer problems, in any case then I use less (N_Periods)
       
        For N=0 To 504
        XX[N]=XX[N+16]
        YY[N]=YY[N+16]
        Next
       
       
        ' Downsizing the number of samples
        If FL=0 Then
        Dec N_Periodi,10 ' If simultaneous sampling is at 330 Ksps then consider 10 samples per quarter period
        EndIf
        ' I create the vector with the component transmitted in quadrature ZZ[N].
        For N=0 To N_Periodi
       
        SW[N]=YY[N]     ' Buffer for YY[N]
        ZZ[N]=YY[N+10] 
       
        Next
       
       
        For N=0 To N_Periodi
        ' For sign inversion I subtract the variable to 1023 (10-bit ADC)
        YY[N]=1023-ZZ[N] ' I apply the correction. The in-phase component to be calculated is equal to -ZZ[N].
        ZZ[N]=1023-SW[N] ' I apply the correction. The quadrature component to be calculated is equal to -YY[N] (- VR16)
       
        Next
       
       
        ' Calculating the mean value of the received signal
        A1=0
        For N=0 To N_Periodi
        A1=A1+XX[N]
        Next
        A1=A1/(N_Periodi+1)  ' Mean
       
        ' Calculating the amplitude of the received signal
        P_media=0
        For N=0 To N_Periodi
        P_media=P_media+(XX[N]-A1)*(XX[N]-A1)
        Next
        P_media= P_media  / (N_Periodi+1)
        P_media=Sqr(2*P_media) ' amplitude of the received signal
       
        ' Calculating the average value of the transmitted signal
        A2=0
        For N=0 To N_Periodi
        A2=A2+YY[N]
        Next
        A2=A2/(N_Periodi+1)
       
       
        For N=0 To N_Periodi     ' Transform the original signals into a two-level signal pair
       
            If XX[N]> A1 Then
               XX[N]=1
       
               Else
               XX[N]=0
            EndIf
       
            If YY[N]> A2 Then
               YY[N]=1
       
               Else
               YY[N]=0
            EndIf
       
            If ZZ[N]> A2 Then     ' Being ZZ a 10 samples delayed copy of YY, the mean value does not change
               ZZ[N]=1
       
               Else
               ZZ[N]=0
            EndIf
       
        Next
       
        M1=0
        M2=0
       
        For N=0 To N_Periodi
        M1=M1+(XX[N] ^ YY[N]) ' Calculating the Exclusive OR between the received signal XX[N] and the transmitted signal in phase YY[N]
        M2=M2+(XX[N] ^ ZZ[N]) ' Calculating the Exclusive OR between the received signal XX[N] and the transmitted quadrature signal ZZ[N]
        Next
       
        PHOut=M1*3.14/(N_Periodi+1) ' Estimated phase shift XX vs YY  within [0°-180°]
        PHOut1=M2*3.14/(N_Periodi+1)' Estimated phase shift XX vs ZZ  within [0°-180°]
       
        Result = PHOut

EndProc




Proc Write_Trimmer (AA As Byte)          ' Set the digital potentiometer
    I2COut SDA_Pin, SCL_Pin, TPL_WRT,[0x00,AA]
    DelayMS 100

EndProc


Proc Read_Trimmer (), Byte          ' Read the digital potentiometer
    Dim Resistenza As Byte
   
    I2COut SDA_Pin, SCL_Pin, TPL_WRT,[0x00]
    I2CIn SDA_Pin, SCL_Pin, TPL_RD,[Resistenza]
    Result=Resistenza
EndProc


Proc I2C_LCD_XY (AA As Byte, BB As Byte)          ' Cursor at the coordinates (X,Y)
    Dim wiz As Byte
    Dim woz As Byte

    If AA>15 Then
    AA=15
    EndIf

    If BB>2 Then
    BB=2
    EndIf

    wiz=((64*BB+AA))
    woz=(wiz|128)
    I2COut SDA_Pin, SCL_Pin, LCD_ADD,[0x80,woz]
   
EndProc

Proc I2C_LCD_Clear()                              ' Display erase
    I2COut SDA_Pin, SCL_Pin, LCD_ADD,[$0x80,$0x01]
    DelayMS 150
    I2COut SDA_Pin, SCL_Pin, LCD_ADD,[$0x80,$0x02]
    DelayMS 150
EndProc

Proc I2C_LCD_Init()                               ' Display INIT
    I2COut SDA_Pin, SCL_Pin, LCD_ADD,[$0x80,%00101000]
    DelayMS 5
    I2COut SDA_Pin, SCL_Pin, LCD_ADD,[$0x80,%00001100]
    DelayMS 5
    I2COut SDA_Pin, SCL_Pin, LCD_ADD,[$0x80,%00000001]
    DelayMS 5
    I2COut SDA_Pin, SCL_Pin, LCD_ADD,[$0x80,%00000110]
    DelayMS 5
EndProc

Proc I2C_RGB_Init()                               ' RGB INIT
    I2COut SDA_Pin, SCL_Pin, RGB_ADD,[$0x0,$0x0]
    DelayUS 200
    I2COut SDA_Pin, SCL_Pin, RGB_ADD,[$0x01,$0x20]
    DelayUS 200
    I2COut SDA_Pin, SCL_Pin, RGB_ADD,[$0x08,$0xFF]
    DelayUS 200
EndProc

Proc I2C_RGB_Colour(AA As Byte, BB As Byte, CC As Byte)   ' RGB Color set
    I2COut SDA_Pin, SCL_Pin, RGB_ADD,[$0x04,AA]
    DelayUS 200
    I2COut SDA_Pin, SCL_Pin, RGB_ADD,[$0x03,BB]
    DelayUS 200
    I2COut SDA_Pin, SCL_Pin, RGB_ADD,[$0x02,CC]
EndProc

Proc I2C_LCD_Write(WW[17] As Byte  )               ' Write TEXT 
    Dim X As Byte
    Dim Q As Byte
    X=0
    While WW[X]<>0
    Q=WW[X]
    I2COut SDA_Pin, SCL_Pin, LCD_ADD,[$0x40,Q]
    DelayUS 50

    X=x+1
    Wend

EndProc


Proc I2C_Bat(ZZ As Byte)                           ' Display battery status
    I2C_LCD_XY(15,0)
    I2COut SDA_Pin, SCL_Pin, LCD_ADD,[$0x40, ZZ]
    DelayMS 50
EndProc



Proc Calibrazione()
    ' Performs CALIBRATION - Hold the coil 1-2 cm from terrain and away from metallic objects -
    ' Acquire TX and RX samples
    IEC0bits_T1IE = 0 ' Disable the Timer1 interrupt
    I2C_LCD_Clear()
    I2C_LCD_XY(0,0)
    I2C_LCD_Write ("  Calibration  ")
    I2C_LCD_XY(0,1)
    I2C_LCD_Write ("-Hold the Coil-")
    DelayMS 1000
    FL=0
    Soglia=0
    PH0=0

    For a=0 To 520
    YY[a]=0
    XX[a]=0
    ZZ[a]=0
    SW[a]=0
    Next

    Repeat


        KK=0
       
        IEC0bits_DMA1IE  = 1; //DMA interrupt on
       
        While KK <=64:Wend   ' Acquire FL*8 samples per channel
   
        IEC0bits_DMA1IE  = 0; //DMA interrupt off
   
        ' Calculating power and shift
        Conditioning()
   
        Inc FL
        PH0=PHOut ' Initial value
       
        Soglia=Soglia+P_media
       
    Until FL=30
   
    Soglia=(Soglia/30)  ' Mean Value obtained from Calibration
   
    IEC0bits_T1IE = 0 ' Disable the Timer1 interrupt
    I2C_LCD_XY(0,0)
    I2C_LCD_Write (" Search Active  ")
    I2C_LCD_XY(0,1)
    I2C_LCD_Write ("   ----------   ")
    IEC0bits_T1IE = 1 ' Enable the Timer1 interrupt

 EndProc



 Proc ADC_Battery ()


 AD1CON1bits_ADON=0  ' Turn off ADC module
 DMA1CONbits_CHEN=0  ' Turn off DMA module
 AD1CON2 = 0
 AD1PCFGLbits_PCFG9 =0  'Input from AN9 for battery check
 DMA1CONbits_CHEN=1  ' Turn off DMA module
 AD1CON1bits_ADON=1  ' Turn on ADC module


 EndProc

 Proc Disp_Bat()

 ' Check battery levels and display the corresponding icon
           
            IEC0bits_T1IE = 0 ' Disable the Timer1 interrupt
            IEC0bits_T3IE = 0 ' Disable the Timer3 interrupt
           
            ADC_Battery () ' Configure ADC for battery reading
             
            Bat=ADIn 9    ' Read battery status
            DelayMS 1  ' Wait for 1 ms
           
             
             If Bat>=553 Then   ' Bat >= 5.7 V
                I2C_Bat (5)
             EndIf

             If Bat>=533 And Bat<553 Then  ' 5.5 V <=  Bat < 5.7 V
                I2C_Bat (4)
             EndIf

             If Bat>=515 And Bat<533 Then  ' 5.3 V <=  Bat < 5.5 V
                I2C_Bat (3)
             EndIf

               If Bat>=495 And Bat<515 Then '5.1 V <=  Bat < 5.3 V
                I2C_Bat (2)
             EndIf

               If Bat<406 Then 'Bat < 4.2 V
                I2C_Bat (1)
             EndIf
           
             ADC_Configure () ' Configure ADC for detection
             DMA_Config()     ' Configure ADC for detection
             
             IEC0bits_T3IE = 1 ' Enable the Timer3 interrupt
             IEC0bits_T1IE = 1 ' Enable the Timer1 interrupt 
           
 EndProc

Gamboa

Hi Max,

Thanks for sharing.

It is a very interesting project. I am especially interested in the use of DMA for reading the ADC. I have to study it carefully because I don't understand it very well.
The software refers to two include files: PWM_33_Max.inc and ADin.inc. Could you share these files too?

It would be nice to put this piece of code in the examples section or in projects.

Regards,
Gamboa

Long live for you

Wimax

Hi Gamboa,

"PWM_33_Max.inc" comes from "PWM_24.inc" that I customized in order to generate frequencies over 65535 Hz, in this case you can use "PWM_24.inc" without any problems.
"PWM_24.inc" and "ADinc.inc" are available from standard Les package in "(...)\PDS\Includes" folder.


I attached a photo of the PCB as 'proof of the crime' ;D

SeanG_65

VERY interesting thread this, next step, two frequencies  ;D

Wimax

Quote from: _65 link=msg=16379 date=1714262403VERY interesting thread this, next step, two frequencies  ;D
;D

I intentionally left space on the pcb for future modifications and additions.
This version still needs deepening, certainly the improvement of target detection to then allow the leap from engineering data to a classification of the target itself, but this requires time and perseverance for rigorous experimentation.
The micro offers a good amount of program memory, unfortunately not too much RAM, but it can be squeezed some more.
A generational leap might be to upgrade to the EP series of Pic33s, perhaps a dspic33ep512mu810, with even more program memory, significantly more RAM, higher clock, native DSP capabilities etc. , but this requires a nontrivial redesign both at hardware and firmware level.
Meanwhile, I confidently await feedback from enthusiasts ;D