Using flow sensor to control dc motor for water sampling

Hello, I just started using arduino about 3 days ago. Im trying to design a wastewater sampling pump to collect composite samples because commercial ones are expensive. The program is to run a peristaltic pump for 1 minute, and then reverse the motor for 1 minute to purge the lntake line of liquid, and then rest for 10 minutes before starting again. I was able to do this successfully, but I want to start the program with a button. I added a push button to my circuit, put when I press the button it only runs the loop once, and I'd like it to run infinitely or for at least 24 hours, and then stop the program when button is pushed again. Could you please assist? circuit and code is included

int buttonStateStart = 0;


void setup() {
     // initialize digital pin 4 and 3 as an output
 
     pinMode(2, INPUT);
     pinMode(3, OUTPUT);
  	 pinMode(4, OUTPUT);
}
void loop() {
  
     buttonStateStart = digitalRead(2);
  if (buttonStateStart == HIGH) { //turn loop on
     digitalWrite(3, HIGH); // Turn on pin 12 to high current output
     digitalWrite(4, LOW);
     delay(3000);     // Remain on for 30000 milliseconds (30 seconds)
     
     digitalWrite(3, LOW); // Turn on pin 12 to high current output
     digitalWrite(4, LOW);
     delay(3000);      // Delay 3 seconds before reversing motor so it doesnt break
  
     digitalWrite(3, LOW);     // Turn off pin 12 to no current output
     digitalWrite(4, HIGH);
     delay(3000);   // Reverse motor to purge for 30000 milliseconds (30 seconds)
     
     digitalWrite(3, LOW); // Turn off motor for before starting again (30 seconds)
     digitalWrite(4, LOW);
      delay(3000);
    }
}

Put this at the end of setup()

while (digitalRead(2) == LOW);

and remove the button checks from loop().

Welcome to the forum... and good work posting your code correctly... something that not many first timers get right.

The problem with your code is that as soon as you release the button the if statement is no longer true so the pump code doesn't run again. You need to keep track of the fact that the button was pressed, and use that to either run the pump code or not.

Something like this.

boolean runPump = false;

void setup() 
{
  pinMode(2, INPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
}

void loop() 
{
  if (digitalRead(2) == HIGH) // Button pressed?
  {
    runPump = !runPump;       // Toggle the pump state.
  }

  if (runPump)                // Do we need to run the pump sequeuce?
  {  
    digitalWrite(3, HIGH);    // Turn on pin 12 to high current output
    digitalWrite(4, LOW);
    delay(3000);              // Remain on for 30000 milliseconds (30 seconds)

    digitalWrite(3, LOW);     // Turn on pin 12 to high current output
    digitalWrite(4, LOW);
    delay(3000);              // Delay 3 seconds before reversing motor so it doesnt break

    digitalWrite(3, LOW);     // Turn off pin 12 to no current output
    digitalWrite(4, HIGH);
    delay(3000);              // Reverse motor to purge for 30000 milliseconds (30 seconds)

    digitalWrite(3, LOW);     // Turn off motor for before starting again (30 seconds)
    digitalWrite(4, LOW);
    delay(3000);
  }
  else                        // Turn the pump off.
  {
    digitalWrite(3, LOW); 
    digitalWrite(4, LOW);
  }
}

This code should work but it has one major problem... and that is that the button is only checked once per loop... and your loop is very slow because you are using delay() statements to control the timing. These statements block all code from running, so are not recommended when you need to do more than one thing at a time - like checking buttons.

A better way is to use millis() for your timing. The following provides a fairly basic example of how to do this.

Understanding this technique is pretty fundamental when working with the Arduino... so I highly recommend trying to understand how it works, and then applying that to your particular problem.

Oh, sorry, I forgot that part. @red_car solution is better, my suggestion will not do that. With my solution you could press the Uno's reset button to stop the motor. You can attach an external reset button if that would be better.

Put @PaulRB ‘s code at the beginning of the loop and use toggle switch button

Hi @agz2007 ,

with using delay() you would have to press the push-button in exact that microsecond
when the final delay()

has finished.
This would be very unconvenient.

I guess you want the code that whenever you press the button regardless of what activity the code is doing sucking/pausing/purging the pump shall be stopped.
And if you press the button again start pump/purge sequence new

To make a program responsive all the time even if the program is "waiting" this requires a different style of coding which is called non-blocking.

This has radical consequences to code in a different way.

  • The code is organised in functions, where each part of the code that builds a senseful sub-unit is put into its own function. These functions shall have a self-explaining name.

  • all functions must be coded in a way to quickly run down in a jump in / jump out manner. This enables to do multiple things fast after each other so it seems all things happen in parallel.

You have to check for button-presses all the time

  • while your pump is sucking in

  • while the code is pausing with pump switched off

  • while your pump is purging out

  • if your code should "wait" some time before going on with a new action this must be done with non-blocking timing

A program with a functionality of be responsive to buttonpresses all the time while executing a sequence of steps suck in / wait / purge out

this program jumps up to a medium advanced level with medium advanced programming-techniques

The sequence of suck in / wait / purge out /
is coded using a state-machine

I estimate your first reaction to the code below will be oh my god what is all this???
Well (just) medium advanced coding.

But this code is well organised in parts where each part does a sensefull thing like
example

void SuckingIn() {
  digitalWrite(L293_3A_Pin, HIGH);
  digitalWrite(L293_4A_Pin, LOW);
}

set the two IO-pins that are connected to the L293D driver-chip to make the DC-motor run for sucking

all details have self-explaining names

So give yourself 30 minutes time to read in the code scrolling up and down to understand how it works together.

You will still have mutliple questions. Just ask them in postings

The code uses the library toggle.h which "delivers" a functionality that a momentary push-button can be used as a toggle-switch

here is the code

/* explanation of the most important details:

  realising a functionality where using a momentary push-button
  acts as a toogle-switch
  push       => activated  push again => DE-activated
  push again => activated  push again => DE-activated
  etc. etc. ...
  This needs quite some code. This code is well organised in MULTIPLE functions
  where each function is a senseful SUB-program

  This is the reason why function loop looks super-short:

  void loop () {
    myButton.poll();

    latchedButtonState = myButton.toggle();
    execute_if_Active(latchedButtonState); // function that does what its name says
  }
  Huh that's all? No. Of course there is more (below function loop)

  If you just want to APPLY the functionality to your own code
  replace the code inside function    void my_Action()
  with your own code that shall only be  executed if you activated execution
  with the momentary push-button

  I recommend to RENAME the function my_Action() to a SELF-explaining name
  This will cause a compiler-error which shows you where you have to rename
  the function-call of this function too (this is inside function execute_if_Active
  If you want to understand how it works read and analyse the rest of the functions.

*/

#define ProjectName "Toggle.h-library demo start/stop executing-code on buttonpress"

const byte switchedON  = 1;
const byte switchedOFF = 0;

const byte StartStopButtonPin  =  2; // uses a button connected to IO-pin 4
const byte L293_3A_Pin         =  3;
const byte L293_4A_Pin         =  4;


// take Arduino-Uno / Mega Onboard LED
// as indicator to show status of latched switch
const byte OnBoardLedPin = 13;//LED_BUILTIN;

unsigned long myCounter = 0;
unsigned long myTimer;

// constants with SELF-explaining names for the state-machine
const byte sm_idling       = 0;
const byte sm_StartSucking = 1;
const byte sm_Sucking      = 2;
const byte sm_PumpPausing  = 3;
const byte sm_StartPurging = 4;
const byte sm_Purging      = 5;
const byte sm_FinalPausing = 6;

byte PumpState;

// variables of type unsigned long used for non-blocking timing
unsigned long SuckingStarted = 0;
unsigned long PausingStarted = 0;
unsigned long PurgingStarted = 0;


byte latchedButtonState;


#include <Toggle.h> // this line adds the code inside the file Toggle.h that makes it work

Toggle myButton(StartStopButtonPin); // create object that uses IO-pin StartStopButtonPin


void setup() {
  Serial.begin(115200); // adjust baudrate in the serial monitor to match the number
  Serial.println( F("Setup-Start") );
  printFileNameDateTime();

  //pinMode (StartStopButtonPin, INPUT);
  pinMode(OnBoardLedPin, OUTPUT);

  digitalWrite(L293_3A_Pin, LOW); // initially switch OFF
  digitalWrite(L293_4A_Pin, LOW); // initially switch OFF
  pinMode (L293_3A_Pin, OUTPUT);
  pinMode (L293_4A_Pin, OUTPUT);

  myButton.begin(StartStopButtonPin); // start the button-object
  PumpState = sm_StartSucking;
}


void loop () {
  // the function poll() must be called very often
  // to read in and update the actual state of the button
  myButton.poll();

  // the function toggle() returns the state of a SOFWTARE EMULATED latched button
  // after a first  press of the button the function returns a "1" like I'm in ON-mode
  // after the next press of the button the function returns a "0" like I'm in OFF-mode
  // after the next press of the button the function returns a "1" like I'm in ON-mode
  // etc. etc. etc. etc. etc. ....
  // this means that only AFTER releasing the button and then
  // pressing DOWN the button NEW
  // the value that function toggle() returns is CHANGED
  latchedButtonState = myButton.toggle();
  execute_if_Active(latchedButtonState); // function that does what its name says

  // switch onboard-LED to state of the latched switch to indicate ON/OFF
  digitalWrite(OnBoardLedPin, latchedButtonState);
}


// helper-function to visiulise in the serial monitor
// what the code does
void printStateChangeToSerialMonitor() {
  static bool lastSwitchState; // static make values persistant

  // check if state has CHANGED
  if (myButton.toggle() != lastSwitchState) {
    // only if state of switch has CHANGED

    lastSwitchState = myButton.toggle(); // update variable lastSwitchState

    if (myButton.toggle() == switchedON) {
      Serial.println();
      Serial.println("start executing");
      Serial.println();
    }
    else { // not activated
      Serial.println();
      Serial.println("stopp executing");
      Serial.println();
    }
  }
}



void execute_if_Active(bool p_IsActivated) {

  printStateChangeToSerialMonitor();

  if (p_IsActivated) {
    // function SuckInPurgeOutSequence() is only executed if parameter p_IsActivated is true
    // this parameter "p_IsActivated" is set true or false
    // with each press of the button called "StartStopButtonPin"
    SuckInPurgeOutSequence();
  }
  else { // if DE-activated prepare new start of sequence
    PumpState = sm_StartSucking;
  }
}

void SuckingIn() {
  Serial.println(__func__);
  digitalWrite(L293_3A_Pin, HIGH);
  digitalWrite(L293_4A_Pin, LOW);
}

void PurgeOut() {
  Serial.println(__func__);
  digitalWrite(L293_3A_Pin, LOW);
  digitalWrite(L293_4A_Pin, HIGH);
}


void PumpOFF() {
  Serial.println(__func__);
  digitalWrite(L293_3A_Pin, LOW);
  digitalWrite(L293_4A_Pin, LOW);
}


void SuckInPurgeOutSequence() {

  PrintStateChange();

  switch (PumpState) {

    case sm_idling:
      // simply do nothing
      break; // immidiately jump down to END-OF-SWITCH


    case sm_StartSucking:
      SuckingIn();
      SuckingStarted = millis(); // store snapshot of time into variable named "SuckingStarted"
      PumpState = sm_Sucking;
      break; // immidiately jump down to END-OF-SWITCH


    case sm_Sucking:
      if ( TimePeriodIsOver(SuckingStarted, 3000) ) {
        PumpOFF();
        PausingStarted = millis(); // store snapshot of time into variable named "PausingStarted"
        PumpState = sm_PumpPausing;
      }
      break; // immidiately jump down to END-OF-SWITCH


    case sm_PumpPausing:
      if ( TimePeriodIsOver(PausingStarted, 1000) ) {
        PumpState = sm_StartPurging;
      }
      break; // immidiately jump down to END-OF-SWITCH


    case sm_StartPurging:
      PurgeOut();
      PurgingStarted = millis(); // store snapshot of time into variable named "PurgingStarted"
      PumpState = sm_Purging;

      break; // immidiately jump down to END-OF-SWITCH


    case sm_Purging:
      if ( TimePeriodIsOver(PurgingStarted, 3000) ) {
        PausingStarted = millis(); // store snapshot of time into variable named "PausingStarted"
        PumpOFF();
        PumpState = sm_FinalPausing;
      }
      break; // immidiately jump down to END-OF-SWITCH


    case sm_FinalPausing:
      if ( TimePeriodIsOver(PausingStarted, 1000) ) {
        PumpState = sm_StartSucking;
      }
      break; // immidiately jump down to END-OF-SWITCH

  } //END-OF-SWITCH
}


void PrintStateChange() {
  static byte lastState;

  if (lastState != PumpState) {
    Serial.print(" changed from state ");
    Serial.print(lastState);
    Serial.print(" to new state ");
    Serial.print(PumpState);
    Serial.print(": ");
    lastState = PumpState;

    switch (PumpState) {

      case sm_idling:
        Serial.println("sm_idling");
        break; // immidiately jump down to END-OF-SWITCH

      case sm_StartSucking:
        Serial.println("sm_StartSucking");
        break; // immidiately jump down to END-OF-SWITCH

      case sm_Sucking:
        Serial.println("sm_Sucking");
        break; // immidiately jump down to END-OF-SWITCH

      case sm_PumpPausing:
        Serial.println("sm_PumpPausing");
        break; // immidiately jump down to END-OF-SWITCH

      case sm_StartPurging:
        Serial.println("sm_StartPurging");
        break; // immidiately jump down to END-OF-SWITCH

      case sm_Purging:
        Serial.println("sm_Purging");
        break; // immidiately jump down to END-OF-SWITCH

      case sm_FinalPausing:
        Serial.println("sm_FinalPausing");
        break; // immidiately jump down to END-OF-SWITCH

      default:
        Serial.println("unknown state");
        break; // immidiately jump down to END-OF-SWITCH
        
    } // END-OF-SWITCH
  }
}


// helper-function ignore at first
void printFileNameDateTime() {
  Serial.print( F("File   : ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("Date   : ") );
  Serial.println( F(__DATE__) );
  Serial.print( F("Project: ") );
  Serial.println( F(ProjectName) );
}


// ignore at first
// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long & startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

best regards Stefan

The most important concept with the loop function is that it loops. So, you need to get looping code into your head. Anything you code will go round and round really fast (preferably). The way you are thinking is linear and the fast loop appears like a problem so you have filled it with massive delays to make it slow down and do what you want, one thing after another. This seems intuitive but basically your “human” thinking has broken the microcontrollers (uC) main benefit of machine speed. It can loop thousands of times a second and be responsive to buttons, sensors etc in microseconds. A human-timescale “delay” says to the uC to stop and wait and skip checking the button for a few hundred thousand cycles. What a waste!
There are 2 main concepts to understand when thinking loop.

  1. timing
  2. states

Timing must be done, not by delays, but by checking the internal clock (millis) storing a StartTimer variable in your code and then checking it every loop to see if required time has elapsed. The “blink without delay” example explains this in detail. Remember every time through loop the uC is checking the clock but also doing anything else you have coded.

States and state machines are the way you get the uC to remember what bit of code you want it to run otherwise, as you have found it will run the same code over and over again in an endless loop. The simple way to do it is decide how many distinct states your project needs. Eg:
1 waiting for button to be pushed
2 button has been pushed
In the first state all you need to do is continuously loop round checking the buttons. As soon as a button is pushed you need to change the state, so take an appropriately named variable such as “state” and increment it to 1 from 0. Now every time through loop the uC sees that variable is 1 not 0. So it is easy to code; if state ==1 do this new thing.

Next you will need to work on debouncing and detecting the moment a button becomes pushed etc

As an alternative to the code posted by @StefanL38 consider the following.

This uses the exact same timing concept but does not hide the underlying logic in functions. I believe that it is much better for you as a beginner to understand these concepts yourself, rather than relying on code that someone else has written.

uint8_t state;                                  // Keeps track of where we are in the pumping cycle
                                                // 0 = off
                                                // 1 = pumping
                                                // 2 = paused
                                                // 3 = pumping in reverse
                                                // 4 = resting

unsigned long startMillis;                      // Keeps track of millis() when the pumping cycle starts.
unsigned long currentMillis;                    // millis() of the current loop.

void setup()
{
  pinMode(2, INPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
}

void loop()
{
  currentMillis = millis();                     // Get the current value of millis().

  if (digitalRead(2) == HIGH)                   // Is the button pressed?
  {
    if (state > 0)                              // Are we already in a pumping cycle?
      state = 0;                                // Set state to off.
    else
      state = 1;                                // Start the pumping cycle.

    delay(200);                                 // Small delay to debounce the button.
  }

  switch (state)                                // Check which state we are in.
  {
    case 0:                                     // Off state.
      startMillis = currentMillis;              // Keep resetting the start time of the pumping sequence.
      pumpOff();                                // Keep pump off.
      break;
    case 1:                                     // Pumping state
      if (currentMillis - startMillis > 3000)   // Have we been in this state too long?
        state++;                                // Move to the next state;
      else
      {
        digitalWrite(3, HIGH);                  // Turn on the pump (forward).
        digitalWrite(4, LOW);
      }
      break;
    case 2:                                     // Paused state.
      if (currentMillis - startMillis > 6000)   // Have we been in this state too long?
        state++;                                // Move to the next state.
      else
        pumpOff();                              // Pause the pump.
      break;
    case 3:                                     // Reverse state.
      if (currentMillis - startMillis > 9000)   // Have we been in this state too long?
        state++;                                // Move to the next state.
      else
      {
        digitalWrite(3, LOW);                   // Reverse the pump.
        digitalWrite(4, HIGH);
      }
      break;
    case 4:                                     // Rest state.
      if (currentMillis - startMillis > 12000)  // Have we been in this state too long?
      {
        state = 1;                              // Move back to the beginning of the pumping sequence.
        startMillis = currentMillis;            // Reset the start time of the pumping sequence.
      }
      else
        pumpOff();                              // Rest the pump.
      break;
  }
}

void pumpOff()
{
  digitalWrite(3, LOW);              
  digitalWrite(4, LOW);
}

1 Like

one issue with your code is recognizing a button press during a delay.

consider, which will recognize a button press as soon as it occurs and handles a press in the middle of a cycle, completing a "Purge"

#undef MyHW
#ifdef MyHW
const byte pinBut  = A1;
const byte pinMotA = 12;
const byte pinMotB = 13;
# define OneMinute   (2 * 1000L)
#else
const byte pinBut  = 2;
const byte pinMotA = 3;
const byte pinMotB = 4;
# define OneMinute   (60 * 1000L)
#endif

byte butState;

enum { Off, Forward, Pause, Reverse, Idle, Purge };
int state = Off;

const char *stateStr [] = {
    "Off", "Forward", "Pause", "Reverse", "Idle", "Purge" };

unsigned long msecLst;
unsigned long period;

// -----------------------------------------------------------------------------
void motor (
    int   operation)
{
    byte motA = LOW;
    byte motB = LOW;

    switch (operation)  {
    case Forward:
        motA = HIGH;
        break;

    case Reverse:
        motB = HIGH;
        break;
    }

    digitalWrite (pinMotA, motA);
    digitalWrite (pinMotB, motB);
}

// -----------------------------------------------------------------------------
void loop ()
{
    unsigned long msec = millis ();

    byte but = digitalRead (pinBut);
    if (butState != but)  {
        butState = but;
        delay (10);             // debounce

        if (HIGH == but)  {
            if (Off == state)  {
                state = Idle;
                period = 0;
            }
            else  {
                state = Purge;
                motor (Reverse);
                msecLst = msec;
                period  = OneMinute;
            }
        }
    }

    if (Off != state && (msec - msecLst) > period)  {
        msecLst = msec;

        Serial.print (stateStr [state]);
        Serial.print (" - ");

        switch (state) {
        case Idle:
            state  = Forward;
            period = OneMinute;
            break;

        case Forward:
            state  = Reverse;
            period = OneMinute;
            break;

        case Reverse:
            state  = Idle;
            period = 10 * OneMinute;
            break;

        case Purge:
            state  = Off;
            break;
        }

        Serial.println (stateStr [state]);
        motor (state);
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    pinMode (pinBut, INPUT);
    butState = digitalRead (pinBut);

    pinMode (pinMotA, OUTPUT);
    pinMode (pinMotB, OUTPUT);
}

very good idea for compact coding textoutput.
Will use this from now on. Thank you very much @gcjr
best regards Stefan

@gcjr :
do you really think presenting hard to analyse code with no comments are the best way of learning???

what do you think is "hard to analyze"? i think the code is fairly straight forward, uses basic features, symbol names are descriptive and reads well

i doubt ever line needs to be comments, so what 3 things do you think need to be explained?

I estimate almost everything.

The above sentence doesn't tell anything about the TO's programming-knowledge but it is very likely that she/he just started learning

As far as I understand your code the basic thing of your code is
do non-blocking timing as the "outer thing"
where "inside" the timing prepare next state

and things like that

will be very confusing for most beginners
it means pre-setting a state for execution after period has expired.

I totally disagree

Sure milliseconds but for what? The name needs the context of where the value is assigned to understand its use

I guess the next sentence will upset you. I made a conscious decision to write it
It might be that this code is tiny small "peanuts" for you. For a real beginner it is hard to analyse

I call this
"experts blindness for beginner difficulties"

best regards Stefan

i understand the comment, hence my questions. but i don't find your suggestion enlightening

why is changing the state when processing a state confusing? why are the symbol names not descriptive? isn't msec used in the comparison below and then used to set msecLst?

i can understand that someone who just started programming 3 days ago and wrote the code they did, using nothing more than pinMode(), digitalRead() and digitalWrite() may find it hard to understand, if so, they can ask questions and do some studying.

14 replies and @agz2007 has not posted again. Probably not coming back.

I agree, I am thinking of it as a linear function rather than as analytical concept. My first option was to wire a slide switch to the 9V battery that turns power on and off to the motor IC, but was concerned that his method would damage the circuit. I've recieved great guidence in this post, but find it hard to understand given that I've only been working with arduino for a couple days. I'll be sure to look more into millis

Thank you so much, that article was super helpful in helping me understand the code others shared here.

that's definetly an option, though i wouldnt be sure if I'd have to remove the reset button and solder on a button with longer wiring

Holy smokes, that does exactly what I needed the arduino to do, thank you so much for this, it's going to take me a while to understand what the code is doing but excited to learn more since this project could solve a massive problem we have. Some of the commercial water samplers have a function that instead of running for a specified amount of time, they run until a certain amount of liquid is dispensed. Instead of the pump pumping for a certain amount of time, the pump starts and will continue to pump until a flow sensor detects exactly 10ml of liquid, and then stops until the timer starts the pump again using this sensor https://www.amazon.com/gp/product/B07QS17S6Q/ref=ppx_yo_dt_b_asin_image_o00_s00?ie=UTF8&psc=1. Could this be something that would work with your code without radical alterations?

Also, say I wanted to add two more buttons, one to manually prime the pump and one to manually purge it, would I need two l293D's for each button?

My apologies, I usually use stack overflow to ask question related to R and often times wouldn't get a response till a day or two at least. I really love the responsiveness of the community here