Active/driven shield capacitive sensor

I'm looking to make a simple, low-cost water sensor, that can sense whether a container has water in it, or not. This is to be attached to the outside of the container (a 2-3 mm plastic wall, no metal, water with ionic nutrient solution) and all I want it to do is sense whether there's water on the other side or not. By placing this sensor some 5 cm above the bottom it can act as "I'm almost empty, please fill me up" alarm.

It's supposed to be contactless and out of the water, which is why I'm looking at capacitive sensors. The best bet seems to be an active shield type of sensor, as described in this document. I'm mostly looking at topology b), where ground and sensor traces are placed next to one another (possibly multiple as interlocking fingers, to increase overall capacity and sensitivity), and a shield on the other side.

The description is promising: sensitivity is highly directed to one side (the container), no sensitivity on the outside (so no issues with approaching hands or so).

I've been searching for schematics on how to make this work, and seemingly the simplest solution is a combo of a 555 and an opamp (see link above), where the 555 produces the signal and the opamp copies this to drive the shield. A microprocessor can then be used to count the frequency, and do something with it. Looks easy enough to build this circuit; I have some LM538M opamps and TSL555CN timers. For sensor I was thinking of cutting the head off of a a cheap water sensor, adding a piece of aluminium foil to the back of that PCB as shield.

Now I'd love to remove this OpAmp/555 combo and let an ESP8266 control the sensor: producing the signal for both the shield and the sensor, and then sensing the result. If it works, I'm hoping to use an ATtiny or similar microcontroller for this, as all it has to do is operate the sensor and switch (blink?) an LED. Power is not an issue as it's to be powered off a 12V adapter, of course with appropriate regulator and so.

The big problem I have is how to synchronise the signals to the shield and the sensor plate, and at the same time measure the capacitance of the sensor plate.

Can this be done? If so, how?

Any references to someone that did this already? My Google searches came up shockingly empty. It's hard to imagine I'm the first to try this, as low-cost sensors that appear to be using this principle are available.

I am not sure if I understand the shielding correctly. If I do there is no AVR I know that handle it ALONE with any capacitance measuring method I know.
You need some external components. With reducing effect of the shield you maybe can make treshold of 0.9 Vcc, discharge both sensing electrode and shield, then charge shield to Vcc and measure time it takes to charge the electrode via resistor to 0.9 Vcc using internal analog comparator. But it is just an idea, I never tried something like that.
As second thought it may be possible to connect the treshold source (voltage divider) to shield if the resistors are small enough to charge the shield faster than the electrode is charged but large enough an I/O pin can pull it to ground when discharging the electrode…

In the schematic that I linked to, both the shield and the sensor are driven by the same oscillator, to keep them in sync. As I understand it, the OpAmp is to make the shield independent from the sensor. It seems I forgot to link to this specific document. See also attached schematic.

|500x281

The frequency output of the 555 can be measured, and depends on the combined capacitance of C1 and the capacitive plate, the capacitance of which in turn depends on whether it's touched, or a finger is close to the plate.

What I was thinking was in line of driving two pins of a microcontroller in sync, one to measure capacitance, the second to drive the shield.

Yesterday I did some experiments with my NodeMCU and it was a bit of a serious fail. Using the capsense library I could detect touch, but not proximity any further than a sheet of paper (so sensor plate - paper - finger, this was detected fine). When not touched, the library returned -2 all the time (timeout - too large capacitance??), when touch it returned a real value. Adding a small 220 pF cap as suggested made it detect nothing. So I'm stuck for now :-(

I don’t know how exactly capsense measures capacitance. But you need to drive the shield to the same voltage as the sensing plate is. Most methods work basicly by charging or discharging the sensing electrode until treshold voltage is reached and measuring the time needed. If you are using constant current to (dis)charge the plate I believe it is enough to charge shield only to treshold voltage and wait until the sensor reaches the treshold too. However if the treshold is around 1/2 Vcc (as pin treshold) and you are charging the shield to Vcc and you are charging the plate via resistor I doubt the shield does any good. You need either keep the op amp to drive the shield or do some “clever trick” but probably nothig less complex than op amp exists. On the other hand op amp may be too slow for the ATTiny based solution and using something else would be better.

Building an oscillator circuit as above is quite cheap, just looking for a way to reduce the part count. Iirc the 555 oscillates between 1/3 and 2/3 Vcc so the sensing plate would never reach Vcc. Maybe the oscillator + OpAmp is the best way to go. It'd also need just one IO pin on the AVR.

I understand you tried replace the whole circuit with MCU and failed. I see two possible explanations: 1) You tried to drive the shield with another MCU digital pin. This way you are not copying the plate voltage - plate does something like oscillating between GND and Vcc/2 (depending on method) while shield between GND and Vcc. I believe in this scenario the shield does more problems than it solves. 2) If the shield is driven by opamp there is risk the MCU drives plate too fast for the opamp to follow. If you are going this way you should check how fast sensor plate is being (dis)charged and verify the opamp can switch as fast.

I didn't try the shield part yet, at all. My intention is to use just an MCU. At this moment I am studying options and looking for experiences. It's always good to learn from other people's experiences, if only to prevent making the same mistakes :-)

I have tried the capsense library (which somewhat worked, as in it did produce a reading - when touched, not when left alone, not from a short distance). I read the source code, but failed to fully understand it. I just have to try again, and after a few times everything will drop in place. This I want to get working well, so I can start experimenting with different layouts of the sensor and know that if I don't sense anything it's the sensor, not the code. Change one thing at a time!

Alternatively I am considering to modify the code I wrote developing an EC/TDS sensor, as that's also based around an RC circuit and then timing the discharge (albeit variable R with fixed C but that's a minor detail, the principle remains the same).

Building the 555+OpAmp schematic is another thing I want to try. Frequency should be around 10 kHz, so indeed I have to check the spec sheet of both the OpAmp suggested and the LM4?? that I have on hand.

I cannot say I have much experience with capacitive sensing (or anything in electronics in fact) - I am just hobbyist and have little time for experimenting. But anyway capacitive sensing interests me and I would like to learn it too. So I read the documents you linked and shared my ideas.
Now I did some experiments. I fixed aluminium strips on glass with Kapton tape.



I connected it to the circuit and measured capacitance; filling the glass with water increased capacitance. Also touching the electrodes or the glass changed capacitance remarkably.

Then I covered the electrodes with "shield" and did more experiments.



There is schematic of the circuit, I used Arduino Nano (ATMega328p) and the sketch:

/* Digital pin 6 (PD6) is AIN0 (positive input); should be connected to the shield/reference voltage
 * Digital pin 7 (PD7) is AIN1 (negative input); should be connected to the sensing electrode
 * Large pull-up from Vcc should be connected to AIN1
 * Voltage divider should be connected to AIN0
 * Digital pin 5 (PD5) drives the voltage divider to turn it off when discharging electrodes to let them discharge properly
 */

#define ComparatorPins ((1<<PD6)|(1<<PD7))
#define DividerPin (1<<PD5)

unsigned long sumOfResults;

unsigned int takeReading() {
  TCCR1B=0; //stop the timer
  TCNT1=0;  //reset timer
  DDRD&=~ComparatorPins; //let the plates to charge
  PORTD|=DividerPin;
  TCCR1B=(1<<ICNC1)|(1<<CS10); //start the timer with no prescaler
  TIFR1=255;  //clear timer flags;
  while (!(TIFR1&(1<<ICF1))) {  //wait for input capture
    if (digitalRead(7)) { //this should never happen
   //   Serial.println("!");
  //    break;
    }
    if (TIFR1&(1<<TOV1)) { //overflow of the timer
      Serial.println(".");
      TIFR1=(1<<TOV1);
      break;
    }
  }
  PORTD&=~DividerPin;
  DDRD|=ComparatorPins; //discharge shield and sensing plate
  return(ICR1);
  delay(2);
}

void setup() {
  Serial.begin(115200);
  ACSR=(1<<ACIC); //Output of comparator is input of Input Capture of timer 1
  TCCR1A=0;
  PORTD&=~ComparatorPins; //make sure they are LOW/without internal pull-up
  DDRD|=ComparatorPins|DividerPin; //discharge shield and sensing plate
}

void loop() {
  sumOfResults=0;
  for (unsigned int i = 1024; i>0; i--) {
    sumOfResults+=takeReading();
  }
  Serial.println(sumOfResults>>10);
}

It works by discharging both the shield and the sensing plate via pins 5-7. Then pins 6 and 7 go high impedance and work as Analog Comparator inputs while pin 5 powers voltage divider to make power for the shield as well as positive voltage for Analog Comparator. At the same time Timer1 is started. Sensing plate (connected to negative input of the comparator) is slowly charged until it is more than "fixed" voltage of the divider. At the moment current state of Timer1 is saved in ICR1 (Input Capture Register). Average from 1024 readings is printed in Serial when it is ready.

I played mostly with resistor values. The shield decreased sensitivity of the electrodes from touch from outside - but not eliminated it completely. I expected increasing voltage made by the divider will reduce effectivity of the shield but I did not notice this.

For the shield to work the divider must be made from small enough resistors (100k and 33k is too much, 3k3 and 10k was better, 3k3 and 1k is probably best combination in my setup). On the other side too low resistors make the results worse - probably it load the pin drivers too much.
Too high resistor (10M) for the sensing plate make too much noise while too low resistor makes the times too short (330k). It is not surprising.
What troubles me is that filling the glass with water does increase capacitance very little at first. Only when the glass is about half full it starts to grow. I noticed the electrodes are closer to each other at the bottom of the glass - it may be explanation? Or possibly the glass is thicker at the bottom??? If so it is not thicker noticeably. Or something else plays role here?

My conclusion is the shielding works and is possible to do with a few passives and I/O pins. I did not compare it to "proper design" - I cannot say how much worse this design is compared to dedicated ICs mentioned in the documents you linked or a solution made by real EE with proper tools. I hope this will help you a bit.

Awesome, thanks for the reply, I'm going to try to replicate this. Gonna need some work on the code as timers work differently on the ESP8266 (you have millis, micros, and absolute clock counts available).

What I understand from shields is that indeed it doesn't fully eliminate sensitivity from the shield size, but brings it down to something very low. A better constructed shield should also have better results.

The "probe" I used was the bottom part of the water sensor - basically a PCB with about a dozen copper traces, alternating GND and sensor. I expected this to work better as the multiple strips would make for a larger capacitor overall and thus larger absolute difference, but connecting the GND made it not work at all. I haven't tried with just two parallel strips, as I see in most images. I'm not sure if that is a simplification, or that you really just have to use two strips, not multiple alternating strips.

I just disassembled the test setup without further experiments. Did you find anything interesting?

I haven't been able to get capacitive sensing working at all with the ESP8266 (using NodeMCU dev boards, as mentioned in #2, didn't try the barebones one), got some Arduino clones and some ATtiny's in the mail, hope to get them here in a week or so, then will restart the sensing. I have no idea what the problem could be. Tried it a few times, rebuilding the experiment, using different pins, no luck. Tried to do some of the capacitance meters as well, also didn't work for me.

Basically I just don't sense anything without direct touch. I want to be able to sense my hand when it's near the sensor (1-2 cm away) before continuing, just to be sure the method works in the first place.

Also ordered one of the new ESP32 boards, that one claims to have 10 capacitive sensing inputs.

When I have any results I'll definitely post back.

Smajdalf:
I just disassembled the test setup without further experiments. Did you find anything interesting?

Today I finally got around to do some testing.
I took your code (only change: a delay(100) in loop to slow things down a bit) and other info. Two strips; one with a 2M2 pull-up connected to pin 7 (sense), one with a voltage divider (pin 5 - 3.3K - pin 6 - 1k - GND) and connected pin 6 to the second strip. I tried 10M and 4M7 as as well, but less stable.

Extremely unstable: at times I got stable values of around 200 (with variation of 1-2), other times it started to bounce up and down and I got values of around 2000-4000 and lots of lines with just a dot instead of a value.

Connecting the second strip (pin 6) made the stability worse. The moments it was stable it did significantly increase the value measured. Presence of my hand had a clear effect with the output value increasing the closer I got to the wires (from about 15 cm distance I saw effects).

Then filled my plastic container with water, and the value did go up but also became very unstable.

I’m not sure where the problem is. Poor connections maybe - but why would the low capacitance value be the more stable in that case? Sometimes the capacitance jumps. I’m using a solderless breadboard and Pro Mini clone for this.

Time for dinner now; tonight I’m going to dive deeper in this, including understanding your code (not familiar with Arduino’s timers at all, and you do some quite interesting bitwise operations there).

Got it to work - without shield. It is just so sensitive to position of wires and so. Big differences there. Also I'm having problems with the wires making good electrical contact with the aluminium foil that I used for the strips.

When filling my container I got a rather steady increase in numbers. I paid attention to get my strips as straight and as parallel as reasonably possible, and the increase looked pretty linear. So that's a good step! Output went from about 600 to 900.

Then added the shield. Upon adding the shield, capacitance jumped (output about 1100). Upon connecting it to pin 6 I saw the capacitance drop drastically again, output just over 500.

When I approach the sensor with my hand from the shield side, I don't see significant effect until I actually touch the shield, which causes the capacitance output to drop. However when approaching from the inside of the container I see the capacitance increase by 10-30 output points. It is VERY sensitive to actual location - as in, distance to the sense strip.

Filling it with water causes the output to rise to almost 800, so total difference about the same as without the shield, and it's rather linear.

There's not much distance between the sensor strips and the shield, just a layer of sticky tape. The article I linked to was describing a double-sided PCB, so distance of 1.6 mm between the two. I don't know if that makes much of a difference.

Very interesting experiment, this. Now all that's left is make it smaller (about 1 cm square) and see if I can sense whether there's water on the other side, or something else for that matter.

wvmarle:
Filling it with water causes the output to rise to almost 800, so total difference about the same as without the shield, and it’s rather linear.

So to understand you correctly, you got it working with active shielding? Meaning: Volumetric measurements that are relatively precise? Plus the added benefit of shielding?

Would you mind sharing a schematic or similar? I’ve tried using an Arduino UNO with both copper strips and large pieces of alufoil…
To get anything useful, I need rather large pieces of foil. Results with copper strips are just all noise…
Even with foil, I have a tremendous amount of noise.

Long time ago… did some simple tests, never really got around to actively drive the shield.

This is the sketch I used:

/* Digital pin 6 (PD6) is AIN0 (positive input); should be connected to the shield/reference voltage
 * Digital pin 7 (PD7) is AIN1 (negative input); should be connected to the sensing electrode
 * Large pull-up from Vcc should be connected to AIN1
 * Voltage divider should be connected to AIN0
 * Digital pin 5 (PD5) drives the voltage divider to turn it off when discharging electrodes to let them discharge properly
 */

#define ComparatorPins ((1<<PD6)|(1<<PD7))
#define DividerPin (1<<PD5)

unsigned long sumOfResults;

unsigned int takeReading() {
  TCCR1B = 0;                       // Stop the timer1.
  TCNT1 = 0;                        // Reset timer1.
  DDRD &= ~ComparatorPins;          // Let the plates to charge (set both pins 6 & 7 as INPUT).
  PORTD |= DividerPin;              // Set DividerPin 5 HIGH.
  TCCR1B = (1<<ICNC1) | (1<<CS10);  // Start the timer1 with no prescaler (set only CS10).
                                    // ICNC1 = noise canceller.
  TIFR1 = 255;                      // Clear all timer1 flags.
  while (!(TIFR1 & (1<<ICF1))) {    // Wait for input capture.
                                    // TIFR1: Timer Interrupt Flag Register. Registers whether an interrupt took place.
                                    // ICF1: Interrupt Capture Flag. 
    if (TIFR1 & (1<<TOV1)) {        // Overflow of the timer - takes too long to charge.
                                    // TOV1: overflow interrupt bit.
      Serial.println(".");          // No successful measurement done this round.
      TIFR1 = (1<<TOV1);            // Clear the timer1 overflow interrupt flag.
      break;
    }
  }
  PORTD &= ~DividerPin;             // Set DividerPin LOW.
  DDRD |= ComparatorPins;           // Discharge shield and sensing plate (set both pins as output).
  return ICR1;                      // Return the value of the timer1.
                                    // This value is stored the moment the interrupt was received.
}

void setup() {
  Serial.begin(115200);
  ACSR = (1<<ACIC);                 // Output of comparator is input of Input Capture of timer 1
                                    // ACSR: Analog Comparator Control and Status Register.
                                    // ACIC: Analog Comparator Input Capture.
                                    // This compares the analog value of AIN0 and AIN1.
                                    // Nothing set so ACIS1 and ACIS0 are both 0 so interrupt on toggle, so toggles
                                    // the moment AIN1 rises to above AIN0 (the reference & the shield).
  TCCR1A = 0;                       // Clear the timer register.
  PORTD &= ~ComparatorPins;         // Make sure they are LOW/without internal pull-up
  DDRD |= ComparatorPins | DividerPin; // Discharge shield and sensing plate (set all pins OUTPUT).
}

void loop() {
  sumOfResults = 0;
  for (unsigned int i = 1024; i>0; i--) {
    sumOfResults += takeReading();
  }
  Serial.println(sumOfResults>>10);
  delay(100);
}

I used foil strips of about 1 cm wide, worked quite well. Never continued the experimenting though after that, and I really don’t remember how it all was supposed to be connected. I didn’t write most of that sketch myself.