Metering volumes with a hall effect flowmeter and solenoid valve

Hey all,

I am trying to make a single push watering system. I have a flowmeter and solenoid valve from adafruit and a Photon microprocessor.

For this little test program I want to push a button to spill 5 L from one valve and 1 L from the other, with one flowmeter.

In this post @peekay123 shares his code for a test program, but I can’t really tell what its, or why everything is there.

So far this is what I have written trying to count the pulses. Every time I check it I get errors, I change the problem and I get a different error:

class Valve 
{
    int valvePin;
    int waterLimit;
    volatile int pulses;
    int meterPin = 3;
    
    public:
    Valve(int pin)
    {
        valvePin = pin;
        pinMode(meterPin, INPUT);
        pinMode(valvePin, OUTPUT);
        digitalWrite(valvePin, LOW);
        pulses = 0;
    }
    
    void pulser()
    {
        pulses++;
    }

    void Water(int limit)
    {
        attachInterrupt(meterPin, pulser, FALLING);
        waterLimit = limit;
        while(pulses < waterLimit){
            digitalWrite(valvePin, HIGH);
        }
    }
    
};




int button = 2;

Valve valve1(0);
Valve valve2(1);


void waterCycle()
{
    valve1.Water(2250);
    valve2.Water(450);
}

void setup()
{
   
    pinMode(button, INPUT);
    attachInterrupt(button, waterCycle, FALLING);

}

void loop()
{
    
}

So I am aware there is all sorts wrong with this, I am just not sure what exactly.

Thanks for looking!

First of, don’t use mere numbers for pins, please stick with the D0, D1, A0, … convention (Arduino “compatibility” or not).
Next I don’t see where you ever reset pulses after you have watered once.
If you call attachInterrupt() in Water() then you might want to detachInterrupt() once you are done - or you only attachInterrupt() once in the constructor.
If you have a switch or button you should use INPUT_PULLUP instead of INPUT (unless you got an external pull-up).
Your Water() routines are very long running, so they should have a Particle.process() inside their while() loops and you are not allowed to call such long running functions from within your button interrupt handler.
Rather only set a flag in the ISR and call your Water() functions from loop() depending on the state of that flag.

Also consider making your Water() functions non-blocking as currently you’ll first work valve1 and only after that’s finished valve2 but this could be done in parallel.
I’d even put the limit check inside the ISRs, so that you just say go and the ISR cares about the stopping.

I guess that’s enough for the start, before going into attaching class methods to an interrupt.

The link you posted there was not leading to peekay123’s latest code - that’s just a few posts down (and some things have changed since too ;-))

1 Like

You can try something like this, which I could not test, but it compiles:

class FlowMeter{

  public:
    FlowMeter(const int pin);
    void setup ();
    void interruptHandler(void);
    void water(int pin, int ticks);
    bool complete();

  private:
    int _solPin;
    int _irq_pin;
    volatile int _volume;
    bool _state = false;
};

FlowMeter::FlowMeter(const int pin)
{
  _irq_pin = pin;
}

void FlowMeter::setup()
{
  attachInterrupt(_irq_pin, &FlowMeter::interruptHandler, this, FALLING);
  pinMode(_irq_pin, INPUT_PULLUP);
}

void FlowMeter::interruptHandler(void)
{
  _volume--;
  if (_volume < 0)
  {
    _volume = 0;
  }
  if(!_volume && _state)
  {
    digitalWrite(_solPin, LOW);
    _state = false;
  }
}

void FlowMeter::water(int pin, int ticks)
{
  _solPin = pin;
  _volume = ticks;
  pinMode(_solPin, OUTPUT);
  if(_volume)
  {
    _state = true;
    digitalWrite(_solPin, HIGH);
  }
  else
  {
    _state = false;
    digitalWrite(_solPin, LOW);
  }
}
bool FlowMeter::complete()
{
  return !_state;
}

enum SolenoidState{
  COMPLETE = 0,
  VALVE_1,
  VALVE_2,
  BEGIN
};

SolenoidState state = COMPLETE;
SolenoidState lastState = COMPLETE;

const int meterpin = D2;
FlowMeter meter1(meterpin);

const int solenoidPin1 = D4;
const int solenoidPin2 = D5;

char currentTime[10] = "";

int deviceState = -1;

void setup()
{
  meter1.setup();
  Particle.variable("CurrentTime", currentTime);
  Particle.variable("State", deviceState);
  Particle.function("Power", powerFunction);
  Time.zone(-4);
}
void loop()
{
  if(state == COMPLETE)
  {
    //wait for Particle function
  }
  else if(state == BEGIN)
  {
    meter1.water(solenoidPin1, 2250);
    state = VALVE_1;
  }
  else if(state == VALVE_1)
  {
    if(meter1.complete())
    {
      meter1.water(solenoidPin2, 450);
      state = VALVE_2;
    }
  }
  else if(state == VALVE_2)
  {
    if(meter1.complete())
    {
      state = COMPLETE;
    }
  }
  deviceState = static_cast<int>(state);
  [](){static uint32_t clockTimer = 0; if(millis() - clockTimer > 1000UL){char timeBuffer[6] = ""; sprintf(timeBuffer, "%02d:%02d", Time.hour(), Time.minute()); strcpy(currentTime, timeBuffer); clockTimer = millis();}}();
}

int powerFunction(String params)
{
  if(params.toInt() == 1 && state == COMPLETE)
  {
    state = BEGIN;
    return 1;
  }
  return 0;
}
2 Likes

Nicely done :+1:

I’m just a bit worried about this long line here :wink:

   [](){static uint32_t clockTimer = 0; if(millis() - clockTimer > 1000UL){char timeBuffer[6] = ""; sprintf(timeBuffer, "%02d:%02d", Time.hour(), Time.minute()); strcpy(currentTime, timeBuffer); clockTimer = millis();}}();

I think this might need some explanation and reformatting.

  []() {
    static uint32_t clockTimer = 0; 
    if(millis() - clockTimer > 1000UL) {
      char timeBuffer[6] = ""; 
      sprintf(timeBuffer, "%02d:%02d", Time.hour(), Time.minute()); 
      strcpy(currentTime, timeBuffer); 
      clockTimer = millis();
    }
  }();
1 Like

it’s basic millis() timer… a drop-in I use in my projects, tried and tested.

Anyways, it isn’t germane to the example or to what OP is concerned about here… just a way of updating the currentTime, so one can see if the device is updating the cloud in real time.

great job editing though, you really aligned those statements nicely. :wink:

My main “concern” was actually the potentially “confusing” use of []() { ... } (); :wink:

I am also interested what that can be. It looks like anonymous function in other languages, but from the description it should run, not just declare. Can you please explain what C trickery is behind that? :no_mouth:

@lami, it’s a C++ lambda, yes a type of anonymous function.

really nice to use… an ad-hoc function and call, being able to capture variables in scope (but I didn’t do that there).

Edit: I now sort-of regret cutting that out of another project! Well, it sparked a discussion and I guess that’s OK.

3 Likes

So that's where I am supposed to get confused? I think I mighta went a few lines early :wink:

Major thanks to you @ScruffR and @BulldogLowell for your INPUT!! There is a lot here I am unfamiliar with and I am generating all sorts of questions.

First, about my code:

I tried using D at one point and was griefed by the compiler so I abandoned it, I will figure out what I did wrong and put it back into practice

You're right, its not there. I had tried putting it in several places, I figured the most logical place would be in the Water() function just before the while() statement. I think I was told I didn't have it defined in that scope. I tried moving it around and if it was defined where I moved the 'reseter' it wasn't defined in the pulser() or Water() functions.

I think you are pointing out a concern that crossed my mind. Does looping digitalWrite() to the valvePin use up all the processor? And does Particle.process() avoid this? Will I be able to have it hold the valve open without thinking about it until it is time to close? I will research Particle.process()

@BulldogLowell, you created a class for the flowmeter and didn't use one for the valves, Why is this? I didn't mention earlier that I will be adding more valves, having them classed would be nice for that, is there any reason other than there are only two?

This is my first micro controller project, my first C++ and also my first Particle project, I was just gonna get everything working with a button then move to a web controller. At the projects end I will be able to specify a volume per valve and have it all go with one button push.

Thanks again for your patience with me!

Photon

You mentioned that you wanted multiple solenoid valves to access the same flow meter, so I created a FlowMeter object with methods that can affect the multiple solenoids (but one at a time). Building a Valve class seems overkill to me. They have only a pin and a state, and that is nicely dealt with with existing functions. Each valve is controlled by the same ISR callback and that function is "attached" to the FlowMeter object. Because the interrupts are driving the valves, we need not hold in a Water function like your code suggested (I couldn't really see where you were going with your class and methods, but admittedly didn't spend much time with it).

If you used several solenoid valves with several FlowMeters, I may have approached it differently. Actually, if I programmed this from the start, I may not have used a class unless I planned on blowing this up to something bigger.

You can easily add a pushbutton switch to initiate the watering sequence, it is rather trivial. You bought a Particle, so I went right to the Particle.function() to initiate the sequencing of the valves. It seemed like a natural next step for you; starting the watering over the internet.

If that wasn't declared there it would not have been declared one line down where you used it for your while(pulses < waterLimit) either :wink:

It's not what you have in your while() but it's the while() itself which keeps the code from doing the cloud house keeping which would happen each way round loop() otherwise. But this is what Particle.process() does then instead to keep the cloud connection alive.

Once you set a pin HIGH or LOW it will remain in this state until you tell the pin to do otherwise.

Eventually my system will involve many branches, sensors, flowmeters, valves, pumps, and Photons. I am trying to put together a simple prototype. Anything that's not too complicated and I can scale later would be nice to get out of the way. I am still making sense of everything you have going on in your script, it certainly seems much more robust than what I had. I will get it straight and see if I can make it work

I have been looking at some of these Particle functions and it seems it may even be easier to use them than a button, diving straight into web controlling seems to make sense.

Thank you, thank you!

I think there are just a few lines I don’t understand:

void FlowMeter::setup()
{
  attachInterrupt(_irq_pin, &FlowMeter::interruptHandler, this, FALLING);
  pinMode(_irq_pin, INPUT_PULLUP);
}

I only ever saw three parameters for attachInterrupt(). Why have you put & in front of FlowMeter and where does ‘this’ come from?

  if(!_volume && _state)

I don’t know what ! and && do. Is just if they both have values then do?

enum SolenoidState{
  COMPLETE = 0,
  VALVE_1,
  VALVE_2,
  BEGIN
};

Is this like an array of possible values for SolenoidState type variables?

int powerFunction(String params)
{
  if(params.toInt() == 1 && state == COMPLETE)
  {
    state = BEGIN;
    return 1;
  }
  return 0;
}

This is the ‘button’?

Thanks again

You use this to set interrupt handler to a method of a class. You pass method and instance of class (this). It is in docs, but bit buried in.

! negates a boolean, so in case of int it means volume == 0 (because non zero _volume in boolean context as in if() translates to true, ! negating it)
&& is boolean "and", meaning both conditions must be true. Basic boolean operators.

Enum is basically set of named values, yes. You could use just numbers for state, but enum makes code easier to understand and less likely to make an mistake.

1 Like

That was what I meant with that

This might be simplifying things:
The this pointer is a C++ construct that's provided to each object to have some way of accessing itself.
Since each object (meaning its non-static members) can live all over the place (and can even move once instatiated) in RAM you need to keep track of it by means of this pointer.
But the member functions will always be "static". So you need to provide the location of the static function (that's the ampersand & for) but also need to let the code know where to find the non-static members when the system calls that function as ISR.

You can read a bit more here
C++ Member Functions As Function Pointers


I see @lami has already chimed in :+1:

That's where you'll find the docs he mentioned (quite a bit down)
https://docs.particle.io/reference/firmware/photon/#attachinterrupt-

So as long as _volume and _state have values the if() will trigger?

The above statement a bit more elaborate

  //if(!_volume && _state) is the same as
  if(_volume == 0 && _state != 0)

and a lot more elaborate

  if(_volume == 0)  // or (!(_volume != 0)) .. (_volume is unequal zero) is not true (!)
  {
    if(_state != 0)
    {
      // do something
    }
  }

the & operator passes the function "by address" allowing the attachInterrupt() to call a class member function, which is explained here in the particle docs:

https://docs.particle.io/reference/firmware/photon/#attachinterrupt-

This clever way cannot be done with Arduino, AFAIK.

yep. Go and google C++ enum.

yes!

the other folks did a great job explaining the rest, better than I could.