Homemade liquid level sensor circuitry

I cant find a commercial liquid level sensor of any kind that is small enough to fit my confined space (1 cm wide) that is made of appropriate material, sooo, Im thinking of making one. The application is sensing when seawater is at a set level in a tank so a conductive mechanism might work and might be simple enough for even me to build. Maybe Ive figured out the simplest way to wire this, but before I hook it up to my Panda I thought Id run my logic by all you beautiful minds to check my thinking, see if Im going to slow cook or fast fry my Panda, or see if there is a better way.

My Plan:

Have two stainless electrodes, either two wires or one wire and the stainless probe casing. The space between the electrodes will be in the neighborhood of 2-8mm (water retention might be a problem–ignore that, i can blow air or something else.) Wire one electrode to ground. Wire the other to a digital input interrupt pin, pulled high. When there is seawater between the electrodes (the normal situation) the current should run from the input pin through the electrodes to ground and the input pin would be low. When seawater drops lower than the electrodes, current should stop flowing to ground and the pin would be pulled high. So on a high edge interrupt I could turn the fill pump on. I guess in this filling state it would be necessary to re-instantiate the pin as a low edge interrupt if I wanted another event to stop the filling.
Q1: Is this logic correct and is there a chance this would work?
Q2: So I dont see this hurting the Panda circuitry at all. Right?
Q3: Would the amount of power that the sensor draws during the time there is a connection between the electrodes would be small, approx… 3.3V x 20mA = 66mW? Or maybe 3.3V x 4mA = 13.3 mW?

One problem with this scenario might be that 3V3 is too weak for a reliable system with any length to the wires. So I might need to boost the voltage to 5V or even higher. If I stick with 5V, I think I could wire one electrode to a 5V source and the other to an input pin pulled low. In this case,
Q4: Would the pin go high on contact between the electrodes?
Q5: Would I need a resistor to limit the current, and would that be 5V / 20mA = 250 Ohms, minimum?
Q6: Since this scenario would mean that normally (90% of 24 hours , 7 days a week) the 5V is at the input pin, would this mean the poor little Panda would cook? (Eventually I will get a Cobra with 5V pins so would this be a problem in that case?)

Q7: Is there a better way to do this?


The current between the supply pin and an input pin would be very small - microamps.
No resistor needed.

Maybe put an ohm meter across the electrodes and measure the resistance and you can do the math.

If you measure on an ADC pin you’ll be able to see the water level as an analog value. The more water between the electrodes, the lower the resistance.

One problem with this setup is contamination of the electrode due to electrolysis. What I suggest you do is to use one pin as an output (generating 3.3V) and the other as input. Take a reading, then swap it around - make the other pin output and the other input and read. This will make the current flow alternately and there won’t be build-up on the electrodes.

Good luck - and let us know how it works!

Seawater is pretty much the most hostile environment imaginable. Use robust equipment, and set it up such that you can access/repair/replace it on a regular basis.

You may have better luck with an ultrasonic setup, or even a float…

I hate putting the sensor in a fluid with a current as too many bad things can happen. Have you considered using a manometer as then at least the sensor isn’t in the fluid (might be subject to vapor), and it becomes a pressure reading which can give a continuous level rather then a contact type reading.

Hm, I like the manometer idea. Great idea for a module!

If you make a Gadgeteer module let me know and put it in here:



Freescale makes a whole range of sensors. Seems perfect. I can lay out a board if someone else is interested on working on this.

look up the National Semi liquid sensor LM1830 it may no longer be available (or perhaps at a surplus shop)



Thanks for the input. I had neglected the plating problem caused by electrolysis. I may look into Hoyt’s suggestion of the NS level sensor circuit that uses an AC current to mitigate the plating and measures resistance to detect liquid presence. Meanwhile, i’m writing some experimental code which utilizes two pins that are connected to two stainless electrodes. Instead of leaving current flowing when the circuit is closed (water between electrodes) with an interrupt to signal when the circuit opens (liquid level drops below the sensor), a Read() method sets up the two pins, pulling the output pin high and the input low and then reading the input to see if there is a closed circuit. Then the sensor is put to sleep by pulling the output pin to low, leaving both electrodes grounded while waiting for the next call to Read(). Each call to Read() toggles the pins to further mitigate plating. As my application has a normal state with the tank full, a second method, Reading(), is used when the water is low (circuit is open). This method continuously sends current (no electrolysis as there is no water) and sets up an interrupt that calls back when the circuit closes (tank is full). I’m a newbie with this stuff so i hope my basic logic about input and output pins will work.

Just an idea… I have no idea if this works or not but it would be interesting to try. Does water produce any current of it’s own? If you stacked up a Darlington Pair or three would it be possible to detect the existence of water between the sensors like you would with a touch sensor? If so, then you could have interrupt driven detection rather than polling.

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;

        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 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;                      
            anode.OnInterrupt += callback;     

        public void StopRead()
        // Use to stop reading in interrupt mode and stop current to electrodes
            if (intrptMode) {
                intrptMode = false;


        private void Sleep()
        // Stops current to electrodes by pulling the cathode to ground; anode is always grounded

        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

        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("Interrupt read test loop...");
            for (int i = 0; i < interruptIterations; i++) {
                calledback = false;
                Debug.Print("Rising interrupt test...");
                levelSensor.Read(Port.InterruptMode.InterruptEdgeHigh, RisingCallback);
                while (!calledback) ;
                Debug.Print("End rising interrupt test");
                Thread.Sleep(wait * 1000);

                calledback = false;
                Debug.Print("Falling interrupt test...");
                levelSensor.Read(Port.InterruptMode.InterruptEdgeLow, FallingCallback);
                while (!calledback) ;
                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;

Not much to add, but very cool.

Any photos?

Here’s a photo of the electrodes. Not much to them. Haven’t tried to see how far apart they can be.

Cool. I could use this to suddenly wake my toddlers in the middle of the night :smiley:

I’ve used level sensors for liquid Nitrogen that operate based on a change in capacitance.
The sensor is a stainless rod inside a stainless tube. The tube has small holes drilled at the top to allow air to escape and fluid to enter from the open bottom. Perhaps coating the stainless with an enamel to minimize current flow and exposure to the salt water while still being able to sense capacitance change would work in this case?

Joel, I’m looking for some small ss rod or heavy wire w/ insulation to put in the middle of a ss tube to make an electrode similar to that although i need to have a diameter of <8mm overall. Don’t know how to measure capacitance change (don’t really understand what it is!) Bare 316 Stainless holds up well in seawater without current, it will be interesting to see how long it lasts with occasional, and opposite, short pulses. Of course Titanium should hold up even better.

Ian, you mean when they pee?

I don’t remember the exact diameter of the tube, but it was thin walled and the OD was less than 8mm.

The C value of the sensor is used in an oscillator, so as liquid level and therefore C change, so does the frequency.

@ Matt5 - Yes :smiley:

So the sensor was fairly long so the more liquid, the more capacitance and higher the frequency? Cool analog metric.