Building an automatic plant waterer (3/?): capacitive sensor try 2

Finally some progress!

• Part 1: resistive sensing
• Part 2: finding resistive sensing is bad and capacitive sensing is hard
• Part 3: another crack at a capacitive sensor
• Part 4: calibrating the sensor

Day V (weekend 5, has it really been going that long?)

OK, so I’m not really happy about the enameled wire design. It feels like the insulation is a bit fragile and I don’t really feel I know what’s going on well enough to rely on it. So, I’ll so something much better: smear some 5 minute epoxy over some stripboard and hit it with a heat gun…

Yes, this is not at all dubious. Well turns out it is. Who knew? Nonetheless it works decently well, though the insulation only goes a few cm up and I think it’s hovering at around 1GΩ. The capacitances are:

•  Out: 13pF
• Dryish soil: 20pF
• Quite wet soil: 30pF
• Very wet soil: 44pF

Substantially less sensitive than the previous one, but proves the principle. If I can actually sense that level of capacitance using the Arduino, then I can get a nice double sided one made with the good quality thin and robust coating you get on PCBs.

On to the Arduino!

So the Arduino environment doesn’t natively support the comparator. Fortunately its not hard. Just fiddly. As in spending 2 hours on a really silly mistake…

It’s fairly straightforward given the datasheet, specifically section 27 AC (analog comparator). The easiest way to get started is:

• Not using the multiplexer, which means the negative input is AIN1
• Using the bandgap (1.1V reference) for the positive input
• Polling by reading ACSR.ACO (analog comparator status register.analog comparator output)
• No low power stuff (it’s a high power application anyway)

It’s surprisingly easy. The Atmega328p has nice defaults for prototyping (everything starts on), there are not too many registers to swizzle up the pins and its happy to have two functions (GPIO and comparator) on one pin at the same time. The bit that I got hung up on for hours is that AIN1 is the Atmega328-P’s AIN1 pin, not the Arduino’s AIN1 pin. AIN1/PD7 (in Atmel-speak) is actually digital pin 7 in Arduino speak. N00bish mistake but really easy to make.

The basic code to control an LED looks like this:

const int led = 13;

void setup() {
pinMode(led, OUTPUT);

// Set ACME bit to 0 to disable the multiplexer
// This also sets some ADC related flags

// Set the positive input to the bandgap reference.
// This also sets disable to off, interrupts to off
// and a bunch of other stuff to off.
ACSR = bit(ACBG);
}

void loop() {

bool result = ACSR & bit(ACO);
digitalWrite(led, result);
}


It works. Yay. Only slightly dampened by the wild goose chase over pin numbers.

A wild goose, for illustrative purposes. (CC by SA https://commons.wikimedia.org/wiki/File:Greylag_goose_(Anser_anser)_head.jpg)

Day 6 (Weekend 6)

Well, this is interesting. The epoxy based probe (above) is now reading a steady 10M even in dry soil. Looks like that isn’t a long term solution. The best wire based one is now faring a lot better. I’m resigned to either having a custom board made with proper soldermask or using conformal coating.

So back to the circuit. Now because for some reason I’m intent on absolutely minimizing cost, an important part is minimizing the pin usage. So the circuit is simply this:

All the clever bits of the circuit are provided by the microcontroller.

The capacitor charges from the positive rail through a 1M resistor.  Internally, I’ve got the comparator connected to 1.1V. The equation for the voltage is:
$V = V_0(1 - e^{-\frac{t}{RC}}).$
Rearranging gives:
$t = -\ln (1-\frac{V}{V_0})RC$
Substituting in the test capacitor (47pF), the 1M resistor, the 5V supply and the 1.1V reference gives a rather marginal 12μs. For now to make life easier, I’m going to use this circuit:

Making a 1.65V rail with resistors means that the 1.1V threshold is on a much flatter part of the charging curve, increasing the time a lot.

Making a potential divider off the 3.3V rail gives 1.65V for the supply, and a much more generous 50μs. Passives are cheap, and I can greatly extend the charging time pretty easily by dividing down the supply. But we need some code to drive it.  The code implements a basic relaxation oscillator: let the capacitor charge then when the voltage exceeds the threshold, short out the capacitor to restart the cycle, then let the capacitor charge…

void setup() {
// Set ACME bit to 0 to disable the multiplexer
// This also sets some ADC related flags

// Set the positive input to the bandgap reference.
// This also sets disable to off, interrupts to off
// and a bunch of other stuff to off.
ACSR = bit(ACBG);

// set PD7 to either hi-Z or low (depending on DDR)
PORTD &= ~bit(7);
}

void loop(){
DDRD &= ~bit(7); // Set pin 7 to hi-z

//Loop until the AC outputs 0 (i.e. when the capacitor
//exceeds 1.1V)
while(ACSR & bit(ACO)){}

DDRD |= bit(7); // Pin7 to low, emptying the cap

delayMicroseconds(50);
}


And here’s what the voltage looks like in operation:

In order to get nice graphs I either had to touch a grounded thing or switch off my fluorescent desk lamps since they seem to spew noise all over the place.

It’s not really very useful like this since all it’s doing is displaying a nice graph. The scope also disturbs the signal since it’s got a non-trivial capacitance and resistance. To do some further analysis, I wrote the following code:

void setup() {
ACSR = bit(ACBG);
Serial.begin(9600);
}

float o=0, o2=0; //IIR filter state for count and count squared
int i=0;

void loop(){
PORTD &= ~bit(7);
DDRD |= bit(7);
DDRD &= ~bit(7);

uint16_t count=0;
while(ACSR & bit(ACO)){
count++;
}
DDRD |= bit(7);

o = 0.999*o + 0.001 * count;
o2 = 0.999*o2 + 0.001 * count * count;

if(i++%256 == 0){
Serial.print(o);
Serial.print(" ");
Serial.println(sqrt(o2 - o*o));
}
}


This code counts during the charging part of the cycle, then does a moving average on both the count and count squared using a simple IIR filter and prints the running mean and standard deviation of the counts to the serial port. I happen to be a big fan of IIR filters, I think they’re fun, interesting and efficient. Even the simplest one is much better than a naïve moving average. The trick of filtering both the value and value squared is one I’ve used many times for getting a running standard deviation in addition to mean.

By default I get counts of about :

• No touch: 88.2 (σ=4.1)
• Light touch: 110 (σ=5.5)
• No touch with scope: 139

I can even spot proximity, so its really pretty sensitive, and you can see how much the scope loads it down by. It also turns out I was really pessimistic earlier. Reverting back to the simpler circuit, the numbers I get are:

• No touch: 19.9 (σ=1.3)
• Light touch: 26 (σ=1.7)

I can still spot proximity, but only with the aid of the filter: it’s about a count of 0.2 or less. So for fun, I modified the code to turn on the LED when the count exceeds 20.2, and you can see just how sensitive it is:

I think now that having faster charging helps: while there’s more quantization noise in the signal, the comparator changes state on a steeper part of the curve which means that electrical noise has less effect on the time.

Today’s conclusions

1. The capacitive sensor is really good, cheap and easy to make. I’m going to have to use that for other things too
2. It’s definitely the way forward for this project
3. Something worked easily!