Sunday, 24 February 2013

LC Fail!

It's been an interesting, if not ultimately frustrating exercise, trying out using an LC oscillator as a miniature metal detector. The truth is, we didn't get the expected response from our board, even without any metallic pieces present.

A simple PCB connected each inductor and capacitor in parallel, which in turn were connected to a PIC i/o pin:



Here's the board all made up and connected to the PIC (ok, we only soldered four out of a possible eight squares, but it's just a prototype to prove a concept, so we're not too worried about that!)


We quickly knocked up some code to test the board. We're using XOR to detect which pins have gone high/low. This kind of thing often throws up some queries, so here's a quick explanation:

First we get the PORB input.
Let's assume it's 00011000

Now we XOR it with a previous read.
Let's pretend that previously the input was 00001000

So 00011000 XOR'd with 00001000 give us 00010000
This result tells us that PORB.5 has changed. Just by looking at the "before" and "after" port states, we can see that this is correct. So we've identified which pin has changed - but has it gone high or low?

If we AND the result with the "new" read data, we should get a pattern telling us which pins have just gone HIGH: 00010000 AND 00011000 gives us 00010000

Incidentally, if we AND the result with the "old" read data, we get a pattern telling us which pins have just gone low. We're not interested in this information right now, but it's a handy technique to know!

Now we've a way of telling, very quickly, which pins from an entire port have just gone high, we can use this to track how long it take for the oscillating LC part of the circuit to go from high, to low, and back to high again. Here's the code we used:


Define CONF_WORD = 0x3f32
Define CLOCK_FREQUENCY = 20
AllDigital

Config PORTB = Output

Dim i As Byte 'declare a variable
Dim portb_input As Byte
Dim portb_last As Byte
Dim portb_change As Byte
Dim portb_rising As Byte
Dim portb_response(8) As Word
Dim portb_final As Byte
Dim read_count As Word

High PORTB.0
Hseropen 9600 'open hardware uart port for baud rate 9600
WaitMs 1000 'this delay should be used in a real device
Low PORTB.0


i = 0

Hserout "Starting PIC: ", CrLf 'send formatted output to serial port
WaitMs 1000 'this delay should be used in a real device

loop:

     'switch on timer 1
     T1CON.TMR1CS = 0 '(use fosc/4 as time base)
     T1CON.T1OSCEN = 1 '(enable timer1 oscillator)
     T1CON.TMR1ON = 1 '(turn the timer1 on)

     'make all of PORTB an output
     TRISB = 0x00
     'wait a sec
     WaitMs 100
     
     'make all PORTB inputs
     TRISB = 0xff
     
     'wait for the inputs to trail off to zero
     portb_input = 0xff
     
     While portb_input > 0
          portb_input = PORTB And 15
     Wend
     
     'now reset the timer
     TMR1H = 0
     TMR1L = 0

     'get the input from the port
     portb_input = 0
     portb_last = 0
     portb_final = 0
     read_count = 0
     
     While portb_final < 15 And read_count < 1000
          portb_input = PORTB And 15
          portb_change = portb_input Xor portb_last
          portb_rising = portb_change And portb_input
          portb_final = portb_final Or portb_input
          read_count = read_count + 1
          
          If portb_rising.0 = 1 Then
               If portb_response(0) = 0 Then
                    portb_response(0) = (TMR1H * 256) + TMR1L
               Endif
          Endif
          If portb_rising.1 = 1 Then
               If portb_response(1) = 0 Then
                    portb_response(1) = (TMR1H * 256) + TMR1L
               Endif
          Endif
          If portb_rising.2 = 1 Then
               If portb_response(2) = 0 Then
                    portb_response(2) = (TMR1H * 256) + TMR1L
               Endif
          Endif
          If portb_rising.3 = 1 Then
               If portb_response(3) = 0 Then
                    portb_response(3) = (TMR1H * 256) + TMR1L
               Endif
          Endif
                                        
          portb_last = portb_input
          
     Wend
     
     'now write your results out over serial
     Hserout "Read count ", #read_count, " "
     For i = 0 To 3
          Hserout "R", #i, ":", #portb_response(i), " "
     Next i
     Hserout CrLf
          
     WaitMs 1000
     
Goto loop 'loop forever


The idea here is that we send the output pin high, charging the LC circuit. We then turn the pin to an input, wait for the LC/input to drop to 0v, then time how long it takes before the oscillator takes the input high again. The results were quite unexpected:

 

Using an inductor of 1.5mH and a capacitor of 3.3uF, we calculated that the oscillator should be running at about 71khz. But different input pins were returning different timing values. Something didn't look right! We took the board off the PIC and re-ran the code and the results were pretty much the same; the input pins were reading as if they were just floating!

One way to eliminate false reads from floating input pins is to add a pull-down resistor on each one. We did this and re-ran the code:


As expected (except, for some reason, the input on PORTB.1) once the input has gone to zero, it never comes back up to a positive value - any current in the circuit from the LC oscillator is being pulled straight to ground through the resistor.

Conclusion? Epic Fail :(

In fact it's not quite as bad as all that. 
It's not a roaring success.
But for a while it was nice just to get back to making stuff and trying ideas out!