Saturday, 28 January 2017

Capacitive sensing using PIC microcontroller

We've been spending a lot of time with Arduinos in recent months. Which has been fun and quite interesting. But now we're back to prototyping some low-level ideas - and many of the libraries for Arduino either "work" or "don't work". There's little room for tweaking and customising - a lot of the functionality is encapsulated away - and once you get to the point where you're reading through someone else's (often quite poorly written) code, it's often quicker to write your own, from scratch!

One of the things we're looking at is detecting large bodies of liquid (ok, pint glasses filled with beer) for a fun experiment. It might turn into something, or it might just remain one on the ideas board. The first thing that springs to mind with detecting any kind of body of liquid is capacitive sensing.

We've done this in the past, using a "charge and count" digital input method (our touch-sensitive miniature guitars used this approach before we finally settled on the darlington multiplex idea instead, and very early prototypes of electronic Blood Bowl used the same, simple approach). The basic idea is to set a pin as an output, drive it high, turn it into an input and count how long it takes to drain; any addition capacitance added to the pin/pad will make it discharge more slowly and you can use this to detect the presence (or absence) of a finger.

In recent years Microchip have been promoting their mTouch technology but it's basically a variation on using two pins/ADC channels to create a voltage divider and using an analogue input to detect the presence (or absence) of an object. A great explanation of how it works can be found here.

The basic idea is:
  • The design uses at least two i/o pins (even if only one sensor is required, although the second pin can also be used as a sensor, if queried in sequence, so three sensors would require three pins, not four - it is only for one sensor you require more pins than you're testing)
  • Pin A is made a digital output and set high
  • Point the ADC to PinA (this causes the ADC to charge up)
  • Make Pin B (the sensor pin) a digital output and force low (set to ground)
  • Turn Pin B (the sensor pin) into input (TRISx = 1)
  • Point the ADC to PinB (the sensor line). While the ADC charge-and-hold capacitor retains charge, we've effectively created a voltage divider between Pin A and Pin B.
  • Begin ADC conversion
  • Read the values of ADRESH: ADRESL (10-bit analogue value)




This is why we need two pins for a single sensor (but there's no reason we can't swap them over and use PinA as the sensor and PinB as the ADC charge circuit if we need two sensors). When the ADC is pointing to PinA, it's held high and the internal charge-and-hold capacitor that makes up part of the analogue-to-digital circuitry is charged up to 5V (or whatever the supply voltage is).

The input pin is grounded (to put it into a known state) then made an input. When the ADC is pointed to the sensor pin, PinB, we now have the VC(hold) capacitor in parallel to the capacitance of the sensor. We also have a voltage divider - the VC(hold) capacitor is fully charged, and the sensor pad is at zero volts. The input pin reads an analogue value, somewhere between the two.

Any increase in capacitance at the "sensor side" of the voltage divider results in a lower analogue value on the input pin.

We take multiple samples and look for a relatively low or high value on the analogue input to determine whether or not there is extra capacitance (and therefore a larger body of liquid) on the sensor. Relative means we keep a rolling average and look for values that are noticeably larger or smaller than this average, to indicate an object being placed or removed from the sensor.

The overall code looks something like this.

Define CONFIG1 = 0x0804
Define CONFIG2 = 0x1dff
Define CLOCK_FREQUENCY = 32

declarations:
   Symbol tx = PORTA.0
   Symbol rx = PORTA.1
   ConfigPin tx = Output
   ConfigPin rx = Input

   Symbol pin_a = PORTA.4
   Symbol pin_b = PORTA.2
   Dim adc_value As Word
   
   Symbol pin_led = PORTB.6
   ConfigPin pin_led = Output
   
   Dim i As Byte
   Dim last_val As Word
   Dim val_diff As Word
   Dim trigger_threshold As Word
   
   
initialise_chip:
   OSCCON = 11110000b 'oscillator = 32mhz
   WaitMs 10
   APFCON0 = 11000100b 'APFCON

initialise:
   trigger_threshold = 30

loop:
   
   Gosub readsensor_1
   'Serout tx, 9600, #adc_value, CrLf
      
   If adc_value > last_val Then
      val_diff = adc_value - last_val
   Else
      val_diff = last_val - adc_value
   Endif
   
   If val_diff > trigger_threshold Then
      If adc_value > last_val Then
         'this is a rising edge (remove glass)
         Low pin_led
      Else
         'this is a rising edge (glass added)
         If last_val > 0 Then
            High pin_led
         Endif
      Endif
      last_val = adc_value
   Endif
   
Goto loop
End



readsensor_1:

   'make pinA a digital output and set high
   ANSELA.4 = 0 'set to one for analogue, clear to zero for digital
   TRISA.4 = 0 'set to one for input, clear to zero for output
   High pin_a
   
   'point the ADC to pinA to allow the charge-and-hold capacitor
   'to start to charge up (RA4 is analogue channel 3)
   ADCON0 = 00001101b
      
   'make pinB a digital output and set low
   ANSELA.2 = 0 'set to one for analogue, clear to zero for digital
   TRISA.2 = 0 'set to one for input, clear to zero for output
   Low pin_b

   'wait for a bit
   WaitMs 1
         
   'turn pinB into an analogue input
   ANSELA.2 = 1 'set to one for analogue, clear to zero for digital
   TRISA.2 = 1 'set to one for input, clear to zero for output
   
   'point the ADC to pinB to complete the voltage divider effect
   '(RA2 is analogue channel 2)
   ADCON0 = 00001001b

   'read the ADC values
   Adcin 2, adc_value
   
Return



And the result works like this



The capacitive sensing pad is just some Bare Electric conductive paint - so it's got a relatively high resistance. The reason we made the "trigger threshold" a variable is so that different pad materials could be used, as well as catering for different glass thickness and even different fluid types that might affect the overall capacitance.

Do J20 and beer have the same trigger points? Would using copper instead of carbon-power-and-gum make it more or less sensistive? We'll have to find out....

But so far, it's all looking quite positive. It's only a simple test, but it's encouraging to see that the trigger stays set even after you release the glass - but it's not so sensitive that it triggers if you just wave your hand over the sensor.