Have written and tested some code for a conductive liquid level sensor object w/ electrolysis mitigation and it seems to be working fine. I had success with seawater and tap water. Couldn’t detect distilled water. Used a couple of #6 ss machine screws for the electrodes and #24 copper test wire–about 12" from Panda to electrodes. Glitch filter needs to be turned on for interrupt reads or get bouncing and false positives. Glitch filter timing tested fine from 1 millisecond to 1000 milliseconds. Here’s the code for the object and test program.
level sensor object:
using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
namespace LevelSensorTestApp
{
class LevelSensor
/* Represents circuitry for a conductive liquid level sensor with electrolysis effect mitagation.
* Interface:
* bool Read() Reads the sensor after first toggling anode and cathode. Leaves both electrodes grounded (no current).
* Returns true when circuit is closed, i.e. conductive liquid present between electrodes, false if circuit is open.
* If called when sensor is continously reading in interrupt mode, will stop continous reading and then read.
*
* void Read(Port.Interrupt.InterruptMode, NativeEventhandler) Sends current to the sensor continuously and calls back
* to NativeEventHandler on occurance of Port.Interrupt.InterruptMode. If called when sensor is already reading in
* interrupt mode will stop the reading and restart with specified interrupt and callback. Leaves current flowing
* to electrodes. Gltich filter should be set to true or may experience false positives and bouncing.
* GlitchFilterTime has been tested as low as 1 millisecond and high as 1000 with success.
*
* void StopRead() Stops a continuous interrupt read, and grounds electrodes. Use after interrupt Read() callback.
*
* struct Electrodes{a,b} Pair of digital i/o interrupt pins which are attached to phyusical electrodes
*
* LevelSensor(Electrodes) Instance constructor. Parameter is an Electrodes struct specifying which two pins are
* attached to the physical electrodes.
*
* A physical sensor is made of two (conductive) electrodes that are placed in a vessel at the desired level to detect the
* presence or absence of a conductive liquid. The electrodes should be spaced close together, i.e. a few millimeters apart,
* however attention should be paid to spacing them so they do not retain drops of liquid between them when the level drops.
* The electrodes need to be wired to a pair of digital i/o interrupt pins. On FEZ processors the pin options are:
* Di0, Di1, Di2, Di3, Di4, Di5, Di6, Di7, Di11, Di12, Di13, Di30, Di32, Di34, Di35, Di36, Di37, Di38, Di39, Di40, Di41, Di42, Di43
*/
{
// TYPEDEFS
public struct Electrodes {
public FEZ_Pin.Interrupt a;
public FEZ_Pin.Interrupt b;
}
// INSTANCE FIELDS
private Electrodes electrodes; // pair of physical pins attached to physical electrodes
private OutputPort cathode; // logical electrode providing current (+, voltage source)
private InputPort anode; // logical electrode returning current (-, ground
private bool intrptMode; // flag signifying sensor is reading w/ interrupt
// CONSTRUCTOR
public LevelSensor (Electrodes electrodes)
{
this.electrodes = electrodes;
intrptMode = false;
CreateElectrodes(Port.InterruptMode.InterruptNone); // create the anode and cathode with no interrupt
Sleep(); // leave both electrodes grounded
}
// PUBLIC INSTANCE METHODS
public bool Read()
// Use to poll sensor, sending current only during read.
// Returns true when circuit closed (conductive liquid present) false when circuit is open.
// Mitigates electrolysis effects by:
// sending DC current only during Read() and leaving electrodes grounded when finished
// toggles electrodes before reading
// Stops reading in interrupt mode if doing so.
{
bool circuitClosed = false; // return value
if (intrptMode) StopRead(); // stop reading if in interrupt read mode
Wake(Port.InterruptMode.InterruptNone); // wake sensor without interrupt, sending current
circuitClosed = anode.Read();
Sleep(); // leave electrodes grounded
return circuitClosed;
}
public void Read(Port.InterruptMode interruptMode, NativeEventHandler callback)
// Use to continuously read from sensor until interrupt specified by 1st parameter.
// Calls back to 2nd parameter on interrupt. Toggles electrodes before reading but
// leaves current running to electrodes. Terminate with call to StopRead() or another call to Read().
{
if (intrptMode) StopRead();
intrptMode = true;
Wake(interruptMode);
anode.OnInterrupt += callback;
}
public void StopRead()
// Use to stop reading in interrupt mode and stop current to electrodes
{
if (intrptMode) {
anode.DisableInterrupt();
Sleep();
intrptMode = false;
}
}
// PRIVATE INSTANCE METHODS
private void Sleep()
// Stops current to electrodes by pulling the cathode to ground; anode is always grounded
{
cathode.Write(false);
}
private void Wake(Port.InterruptMode interruptMode)
// preps sensor for reading by disposing anode and cathode, toggling electrodes
// and re-instantiating anode and cathode in specified interrupt mode with the new electrodes
// and sending current to the cathode
{
cathode.Dispose();
anode.Dispose();
toggleElectrodes();
CreateElectrodes(interruptMode);
}
private void CreateElectrodes(Port.InterruptMode interruptMode)
{
cathode = new OutputPort((Cpu.Pin)electrodes.a, true); // open high, i.e., sending current
anode = new InterruptPort((Cpu.Pin)electrodes.b, true, Port.ResistorMode.PullDown, interruptMode);
}
private void toggleElectrodes()
{
FEZ_Pin.Interrupt temp = electrodes.a;
electrodes.a = electrodes.b;
electrodes.b = temp;
}
}
}
test program:
using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
namespace LevelSensorTestApp
{
public class Program
{
private const int glitchFilterTime = 1;
private const int singleIterations = 3; // number of times to loop for single read
private const int interruptIterations = 3; // number of times to loop for interrupt read
private const int wait = 5; // wait between reads in seconds
private const FEZ_Pin.Interrupt cPinA = FEZ_Pin.Interrupt.Di0;
private const FEZ_Pin.Interrupt cPinB = FEZ_Pin.Interrupt.Di1;
static bool calledback;
public static void Main()
{
LevelSensor levelSensor = new LevelSensor(new LevelSensor.Electrodes {a = cPinA, b = cPinB});
Cpu.GlitchFilterTime = new TimeSpan(0, 0, 0, 0, glitchFilterTime);
Debug.Print("Single read test loop...");
for (int i = 0; i < singleIterations; i++) {
Debug.Print("Conductor present? " + levelSensor.Read().ToString());
Thread.Sleep(wait * 1000);
}
Debug.Print("End single read test loop");
Debug.Print("");
Debug.Print("Interrupt read test loop...");
for (int i = 0; i < interruptIterations; i++) {
Debug.Print("");
calledback = false;
Debug.Print("Rising interrupt test...");
levelSensor.Read(Port.InterruptMode.InterruptEdgeHigh, RisingCallback);
while (!calledback) ;
levelSensor.StopRead();
Debug.Print("End rising interrupt test");
Thread.Sleep(wait * 1000);
Debug.Print("");
calledback = false;
Debug.Print("Falling interrupt test...");
levelSensor.Read(Port.InterruptMode.InterruptEdgeLow, FallingCallback);
while (!calledback) ;
levelSensor.StopRead();
Debug.Print("End falling interrupt test");
Thread.Sleep(wait * 1000);
}
Debug.Print("End interrupt read test loop");
}
static void RisingCallback(uint unused1, uint unused2, DateTime unused3)
{
Debug.Print("*****Rising callback*****");
calledback = true;
}
static void FallingCallback(uint unused1, uint unused2, DateTime unused3)
{
Debug.Print("*****Falling callback*****");
calledback = true;
}
}
}