How to pause a pump for one cycle with a pushbutton

Hi.
I'm working on a system for growing bacteria with periodic addition of new growth medium and removal of used medium. Every four hours, a number of milliliters are pumped in, and the same amount removed from the vials with peristaltic pumps.

Once a day we want to take out a sample for analysis, and at the same time keep the volume konstant in the growth vials. I would like to be able to press a button that would pause the pump that removes medium from the vials for one cycle when we do the sampling.

I don't really know how to go about doing this, so any advice would be helpful.

The project is based on this one: MiCoMo


```cpp
//Code for Arduino Mega1
//This code 

//2. Send voltage to gas sparger. 
//3. Send voltage to pumps for liquid transfer.
//The following time stamps are all in seconds
//Start is earlier for inlet than outlet to compensate for evaporation
//For this, we just need food to colon and colon to waste
//Feed every 4 hours. Transfers for all reactors will happen at exact same time
//Inputting ~4.5ml of content and outputing ~4ml of content
//This ensures an overall ~30 hours retention time
#define TimeStamp1 14000//Food to start inputting liquid into reactors
#define TimeStamp2 14040 //Food to start removing liquid from reactors
#define TimeStamp3 14280 //Time to stop both transfers
#define TimeStamp4 14400 //Time to reset the system

int i; 
int k;
int j;//Placeholder counter

int inputpin = 3; //pin for multichannel pump of adding liquid into reactors
int outputpin = 2;//pin for multichannel pump of removing liquid from reactors
//only 1 pin used because of using 6-channel pump
const int spargerpin = 10;

int timer = 0;//Timer for liquid transfer
int tspa = 0; // Timer for sparging gas


void setup() {
    Serial.begin(9600);
    pinMode (inputpin,OUTPUT);
    pinMode (outputpin,OUTPUT);
    pinMode (spargerpin,OUTPUT); 
 
}

void loop() {
 
//Now, check if we need liquid transfer
  if ((timer > TimeStamp1) && (timer < TimeStamp3)){
    digitalWrite(inputpin,HIGH);
  }
  else {
    digitalWrite(inputpin,LOW);
  }
  
  if ((timer > TimeStamp2) && (timer < TimeStamp3)){
    digitalWrite(outputpin,HIGH);
  }
  else {
    digitalWrite(outputpin,LOW);
  }
  
  if (timer > TimeStamp4){
    timer = 0;
  }
  timer += 1;

//Now, handles the gas sparging
  if (tspa > 5){//reset timer if it exceeds 5 secs
    tspa = 0;
  }
  tspa += 1;
  if (tspa > 3 && tspa < 5){//At the fifth second, sparge
    digitalWrite(spargerpin,HIGH);
  }
  else {
    digitalWrite(spargerpin,LOW);
  }

 //After everything, wait for 1 sec
  delay(1000); 
  Serial.println(timer);
}

There is no difference logically if you move

  timer += 1;

 //  Before everything, wait for 1 sec
  delay(1000); 
  Serial.println(timer);

your essential timing mechanism to the top of the loop().

Then, using a digital input configured in setup(), here pin 11 which I did t check you aren't using

# define pause 11

    pinMode(pause, INPUT_PULLUP);

you can next, after the timing increment, delay and print, test that input

  if (digitalRead(pause) == LOW) {   // switch closed!

// turn off any pumps or whatever that are running, however with digital writing

      return;  // skip all that real pump turning on and off logic
  }

When the switch is closed, time goes forward but the pumps are off.

When you open the switch, the logic will once again proceed through the if/else statements and turn on any pumps that should be.

If you want time to freeze during the pause, move increment of the timer variable to below the if statement that short circuits the loop.

This is crude, but should work.

I am compelled to ask if this timing by counting off one second delays has proved satisfactory. The seconds aren't exact, code does take some time to execute, and the seconds aren't exact as the Arduino isn't clocking perfectly.

For real every day at a certain hour kinda stuff, you woukd be better off using an RTC real time clock and basing actions on the real time.

This would also mean the device would continue on course even if powered off briefly or not so briefly.

I did not test the idea, but I have used it and things even cruder. I do think the rest of the code is tolerant to the trick. I will try it in the lab when I am next there, but probably with time constants so it will take less than 30 hours or whatever to confirm the validity of the code, or find the flaw in my sun-bleached thinking.

HTH

a7

Thanks.
I'll try this.
I honestly don't know how well counting of the seconds works, as I haven't tested the setup yet. It seems to have worked for the group that made it first though. Using a RTC sounds like a much better solution though.

OK, I did what I told you, and it does work fine.

I hadn't noticed the asynchronous sparger activity, so I had to move its timer incrementation up so that it, too would freeze. (I opted for freezing.)

The part that does the thing like I said ripped from functioning code:

 // before everything, wait for 1 sec
  delay(1000); 
  Serial.println(timer);

  if (digitalRead(pause) == LOW) {
    Serial.println("         paused");

    digitalWrite(inputpin, LOW);
    digitalWrite(outputpin, LOW);
    digitalWrite(spargerpin, LOW);

    return;
  }

  timer++;
  tspa++;

This is a basic sketch which kinda begs for a better implementation, and inserting the pause logic points up many questions.

If you do not anticipate and answer all questions in the design phase, the code you write (or find) will provide its own answers, and it would be very lucky if those were correct.

Should sparger continue its lively activity during a pause?

Is stopping either input or output in the middle, then starting it again to finish out the duration what you want?

You have to decide whether to freeze time, or to let it run on, which might could mean partial pump periods or even ones that are totally missed, should the pause be so long.

Avoid saying "we wouldn't do that" - you have a microprocessor mostly spinning its wheels, there is no reason not to build in as much idiot proofing and as many nice touches as you can think up.

BTW input and output are unfortunate variable names in a sketch, misleading maybe and under-informative:

  inputPump
  outputPump

  spargerRelay

  spargerTimer
  pumpTimer

or whatever. A common convention is to capitalize the rest of the words in a wordyVariableName.

Also, I usually sprinkle lotsa print statements in there all over, so my sketches tend to be chatty. It is a nice way to see what s sketch thinks it is up to, viz:

  Serial.println("opening the pod bay doors, Dave.");

I also bumped up the baud rate to something 21th century-ish.

If you stick with a four hour cycle, your logic is very ready to accommodate changes to control by RTC. It would be simple as there is no timing except the one second "tick" currently crudely implemented by the delay and increment.

A but more work and any kind of outputs could be described, either conected or not to the real time the clock would keep.

HTH

a7

Why not have the button stop the motor completely. Then, on the next press, start everything back up again? No worries about time then.

-jim lee

I think I did a bad job describing what I wanted from the code. Thanks for making that clear @alto777 .
What I need is for only the output to skip one pumping cycle when the button is pressed. By that I mean that the rest of the code goes on like before. I want the pump to maintain its timing even though it doesn't pump, so that on the next cycle, it is in synk with the rest again.

The easiest way would be to just ad a switch that cut the power to the motor. There are a couple of reasons why I don't want that solution. One is to remove the human error. It's a bit too easy to forget to switch it back on after hours have passed. The other reason is that we are going to be running experiments for weeks, with sampling every day, so that means weekends too. I would like to be able to take a sample, push a button and leave. With the manual switch I would have to wait around for 4 hours, or come back a second time.

I lost a longer response to an "accident". I will take that up with the cat later. :expressionless:


By skip do you mean both pumps are not run?

Will you ask for the skipped cycle only during the inactive phase?

Is the sparger to continue with its activity?

Do you care about +/- 1 one percent error in the time keeping?

Do you need recovery from power interruption?

Do you need any progress or status indicator showing where in the four hour pattern the device is?

This could be thoroughly totally over-engineered. On the other hand, doing what you have so far asked for is still very easy.

A button would just set a flag. A flag here is simply a variable that takes on but two values, set (1) and not set (reset) (0).

If the flag is set, the pumps do not run.

When the timer recycles (timer = 0), the flag is reset, therefore the pumps will operate next and all subsequent times.

Becuase your loop is crippled, you would have to push the button for long enough so it got seen. I would add an LED to show that skipping had been acknowledged.

To close a brief window where skipping could be requested, but reset, I suggest moving the timing so there is no odd period of inactivity (TimeStamp3 to TimeStamp4) at the end of the cycle - it could just be at the beginning.

a7

By skip, I mean that only the output pump does not run for the next cycle. The input pump and the gass sparging needs to run as before.
I would only ask the for the skipped cycle during the inactive phase.

A +/-1 % error in the timekeeping is acceptable.

It would be nice if it recovered after power interruption, but frankly, those are so rare it's rather unlikely.

I have a computer attached that stores the terminal output, so that should be enough to track the progress.

Setting a flag sounds like a good approach, and I actually thought of the LED :grin:.

OK, so that flag in the quick thing I just tested did exit the loop early.

I moved the two pump controlling if/else statements to the end of the loop(), as it doesn't matter the sparger gets run (or not) before or after deciding about the pimps, so

  if (skipFlag) return;   // all done Mom!

// pump logic

you could do the same, but have only the output pump if/else bleow that, to be skipped on the flag.

I thought of after wondering why my "skip" button wasn't working. :expressionless:

a7

Yes it can be done but you need to learn some more.

First of all, all of your Time variables needs to be unsigned integers.
Where signed values are on a number line, unsigned values map to circles like a round clock. But please note that on a round clock, 12==0.

If it is 4PM, how long since it was 10AM? Count 10 hours counter clockwise fro, 4 and the answer is 6. That works across 12.

The always right (within one turn range, on clock is 12 but with unsigned long time.. 49.7-some days) code is

End Time - Start Time = Elapsed Time. <<=== what engineers use

I have an example of button and blinky led that uses time code though I haven't commented it much yet that may help.

// UnblockedSketchLedAndButton by GoForSmoke 03/08/24
// compiles, untested so far
// expect: ground pin 7 to toggle blink off/on

const byte blinkPin = 13;
const byte buttonPin = 7;
byte buttonStateNow, buttonStatePrev;
const word debounce = 10; // ms delay
word debounceStart; // interval is debounce above
byte blinkState;
unsigned long blinkStart, blinkInterval = 500;
const unsigned long interval = 500;


void setup()
{
  pinMode( blinkPin, OUTPUT ); // LOW by default
  // blinkState is 0 by default
  pinMode( buttonPin, INPUT_PULLUP );
  buttonStateNow = buttonStatePrev = 1;
}

void blink()
{
  static unsigned long waitStart, waitMs;
  if ( blinkInterval == 0 )
  {
    return;
  }

  if ( waitMs > 0 ) // if-timer runs when set then turns itself off
  {
    if ( millis() - waitStart >= waitMs )  // see if wait is over
    {
      waitMs = 0; // wait is over, turn wait off!
    }
  }
  else
  {
    blinkState = !blinkState; // 0 <==> 1
    digitalWrite( blinkPin, blinkState ); // led ON
    waitMs = blinkInterval;
    waitStart = millis();
  }
  return;
}


void button()
{
  static byte State = 0;  //  local variable only the function uses
  static unsigned long waitStart, waitMs;  // local vars

  if ( waitMs > 0 ) // if-timer for state machine delays
  {
    if ( millis() - waitStart >= waitMs )
    {
      waitMs = 0;
    }
  }
  else
  {
    switch ( State )
    {
      case 0 :
        buttonStateNow = digitalRead( buttonPin );
        if ( buttonStateNow == 0 && buttonStatePrev == 1 ) // press detected
        {
          waitMs = debounce;
          waitStart = millis();
          State = 1;
        }
        else if ( buttonStateNow == 1 && buttonStatePrev == 0 ) // release detected
        {
          waitMs = debounce;
          waitStart = millis();
        }
        buttonStatePrev = buttonStateNow;
        break;

      case 1 :
        if ( blinkInterval > 0 )
        {
          blinkInterval = 0; // stop blinking
          digitalWrite( blinkPin, LOW );
        }
        else
        {
          blinkInterval = interval; // blink at interval
        }
        State = 0;
        break;
    }
  }
  return;
}

void loop()
{
  button();
  blink();
}

You get no argument from me on that one :sweat_smile:

I didn't get it right away but in steps between 80-83 and what I knew before, then an article in Creative Computing covering 101 of Main Loop Programming got me writing systems instead of IT Code.

The principles are not hard, the techniques take practice and there's the main curve. The payoff though is incredible.
Multitask on a single thread without using interrupts.
Handle asynchronous inputs sub-ms smooth.

NOTHING will get this across like seeing it work with a loop() counter showing you many tasks all running at over 50KHz. All tasks take one step 50 times every ms on average, sounds good?

So expect to take a while learning pieces and how they fit, you are good for the effort. You will be able to automate but for example if you do motors, first you study motors already knowing automation.

Do Multiple Things At Once 101, Square 1.
Nick Gammon's commonsense tutorial on timer code.

Thanks. This was very useful, and I'm certainly spending time learning more about it.

Thanks to the great input I got here, I managed to write something that does what I need it to for now.
I'll certainly improve upon it, but at least it seems to work.


```cpp
//Code for Arduino Uno
//This code 


//1. Send voltage to pumps for liquid transfer.
//The following time stamps are all in seconds
//Start is earlier for inlet than outlet to compensate for evaporation
//For this, we just need food to colon and colon to waste
//Feed every 4 hours. Transfers for all reactors will happen at exact same time
//Inputting ~4.5ml of content and outputing ~4ml of content
//This ensures an overall ~30 hours retention time
#define TimeStamp1 20//Food to start inputting liquid into reactors
#define TimeStamp2 25 //Food to start removing liquid from reactors
#define TimeStamp3 30 //Time to stop both transfers
#define TimeStamp4 32 //Time to reset the system

int i; 
int k;
int j;//Placeholder counter

int mediaPumpPin = 3; //pin for multichannel pump of adding liquid into reactors
int wastePumpPin = 2;//pin for multichannel pump of removing liquid from reactors
//only 1 pin used because of using 6-channel pump
int buttonPin = 10;
int resetButtonPin = 11;
int led = 12;

int timer = 0;//Timer for liquid transfer
int tspa = 0; // Timer for sparging gas

int sampleFlag = 0;//Flag for sampling

//boolean sampleFlag = false;


void setup() {
    Serial.begin(9600);
    pinMode(mediaPumpPin,OUTPUT);
    pinMode(wastePumpPin,OUTPUT);
    pinMode(buttonPin,INPUT);
    pinMode(resetButtonPin, INPUT); 
    pinMode(led, OUTPUT);
    digitalWrite(buttonPin, HIGH);
    digitalWrite(resetButtonPin, HIGH);
    Serial.println("Setup complete");
 
}

void loop() {

  if (digitalRead(buttonPin) == LOW)
  {
  sampleFlag = 1;
  digitalWrite(led, HIGH);
  Serial.println("Waste removal paused");
  }
  else {
    digitalWrite(led, LOW);
  }
if (digitalRead(resetButtonPin) == LOW)
{
  sampleFlag = 0;
  digitalWrite(led,LOW);
  Serial.println("Waste removal reset");
  }


 
//Now, check if we need liquid transfer
  if ((timer > TimeStamp1) && (timer < TimeStamp3)){
    digitalWrite(mediaPumpPin,HIGH);
    Serial.println("Adding medium");
  }
  else {
    digitalWrite(mediaPumpPin,LOW);
  }
  
  if ((timer > TimeStamp2) && (timer < TimeStamp3) && (sampleFlag == 0)){
    digitalWrite(wastePumpPin,HIGH);
    Serial.println("Removing medium");
  
  }
  else {
    digitalWrite(wastePumpPin,LOW);
  }
  
  if (timer > TimeStamp4){
    sampleFlag = 0;
    digitalWrite(led, LOW);
    timer = 0;
  }
  timer += 1;
  delay(1000);
  }

Looks like it should.

If you don't have pullup resistors on the pushbuttons, you should use pin mode INPUT_PULLUP mode, or you'll get false indications of button activity.

OIC

    digitalWrite(buttonPin, HIGH);
    digitalWrite(resetButtonPin, HIGH);

which I believe turns on the internal pull-ups, so never mind. Or lose those two lines and just use the modern equivalent mode they added at some point.

Also, if I read it correctly, you should lose

  else {
    digitalWrite(led, LOW);
  }

which will turn off the status LED when you are not pressing the button. The status LED should stay on (paused mode) until you press the other button what takes you out of paused.

a7

It works OK. I did remove the else clause to keep the status light on.

I note that the pause resets itself at timeStamp4. Here you only have a two second window of opportunity, but if your real time between timeStamp3 and timeStamp4 is longer, there is more opportunity to engage the pause, but have it reset before the cycle starts over.

#define TimeStamp1 20//Food to start inputting liquid into reactors
#define TimeStamp2 25 //Food to start removing liquid from reactors
#define TimeStamp3 30 //Time to stop both transfers
#define TimeStamp4 32 //Time to reset the system

Since the activity is cyclic, there really is no reason to wait between t3 and t4, you can omit t4 altogether and move all the t1, t2 and t3 up by the difference between t3 and t4

  if (timer > TimeStamp3) {
    sampleFlag = 0;
    digitalWrite(led, LOW);
    timer = 0;
  }

and

#define TimeStamp1 22 //Food to start inputting liquid into reactors
#define TimeStamp2 27 //Food to start removing liquid from reactors
#define TimeStamp3 32 //Time to stop both transfers and reset the timing cycle

The window in the original was two minutes long - of course you wouldn't do that (initiate a pause so near the end of the last activity cycle)... or mightn't you? Since you can protect yourslef with code here, why not do?

a7

First is not blocking and the next hill is state machine that lets the same function work as "steps", what to do each execution depends on what has happened so far. Usually we use switch-case but it can be done without.

Here's another of Nick's Tutorials on Serial w/o blocking.
The State Machine part is the second of 3 parts, can be learned first.
Nick Gammon's State Machine Lesson is inside this.

Make a link for later or mark this post on the forum.
Wait until you have the current lesson before this, it makes it easier!

With just those two parts you have keys to over smooth automation and tasking without a boat-anchor OS. Then the only reason to use interrupts is to catch what very fast polling can't!
From there you build, borrow, invent your way up and out.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.