I ran into a problem regarding the Protothreading library in Arduino. I have created a Button class, which represents a hardware button. Now the idea is that you can attach a ButtonListener to it, which listens to the button. If a button is pressed, then the clicked() function is called.
#include <Arduino.h>
#include <pt.h>
class ButtonListener {
public:
virtual void clicked() = 0;
virtual void longClicked() = 0;
virtual void tapped(int) = 0;
};
class Button {
static const int RECOIL_TIME = 200;
static const int LONG_CLICK_LENGTH = 1000;
private:
int _pin;
ButtonListener *_listener;
struct pt _thread;
unsigned long _timestamp = 0;
int listenerHook(struct pt *pt) {
PT_BEGIN(pt);
this->_timestamp = 0;
while (true) {
PT_WAIT_UNTIL(pt, millis() - _timestamp > 1);
_timestamp = millis();
if (&this->_listener != NULL) {
this->listenForClick();
}
}
PT_END(pt);
}
void listenForClick() {
boolean longClicked = true;
int state = digitalRead(this->_pin);
if (state == HIGH) {
unsigned long timestamp = millis();
while (true) {
longClicked = millis() - timestamp > LONG_CLICK_LENGTH;
state = digitalRead(this->_pin);
if (state == LOW) {
break;
}
}
if (&this->_listener != NULL) {
if (longClicked) {
(*this->_listener).longClicked();
}
else {
(*this->_listener).clicked();
}
}
}
}
public:
Button(int pin) {
this->_pin = pin;
}
void init() {
pinMode(this->_pin, OUTPUT);
PT_INIT(&this->_thread);
}
void setListener(ButtonListener *listener) {
this->_listener = listener;
}
void listen() {
this->listenerHook(&this->_thread);
}
};
Now I've created two implementations of ButtonListener:
class Button12Listener : public ButtonListener {
public:
void clicked() {
Serial.println("Button 12 clicked!");
}
}
The other implementation is a Button13Listener and prints "Button 13 clicked!"
Then let's run the code:
// Instantiate the buttons
Button button12(12);
Button button13(13);
void setup() {
Serial.begin(9600);
button12.init();
button13.init();
// Add listeners to the buttons
button12.setListener(new Button12Listener());
button13.setListener(new Button13Listener());
}
void loop() {
while (true) {
// Listen for button clicks
button12.listen();
button13.listen();
}
Serial.println("Loop ended.");
delay(60000);
}
I expect "Button 12 clicked!" when I click the button on pin 12, and "Button 13 clicked!" when I click the button on pin 13.
But when I try to click on any of the buttons, it is randomly printing "Button 12 clicked!" or "Button 13 clicked!" no matter what button I press.
It look like the protothreads are shared among the buttons or something.
If I check in which order the buttons are called, like this:
button12.listen();
Serial.println("listen12");
button13.listen();
Serial.println("listen13");
then the following outputs:
12
13
12
13
12
12
Thát seems okay.
So what's the problem? What have I missed?
You are completely eliminating the whole point of protothreads by having that while(true) loop in listenForClick. I would do it like this:
PT_BEGIN(thr);
while(1){
// ensure that the pin is low when you start
PT_WAIT_UNTIL(thr, digitalRead(pin) == LOW);
// wait until pin goes high
PT_WAIT_UNTIL(thr, digitalRead(pin) == HIGH);
// insert delay here for minimum time the pin must be high
this->timeout = millis() + 20; // 20 ms
// wait until the delay has expired
PT_WAIT_UNTIL(thr, this->timeout - millis() > 0);
// wait until the pin goes low again
PT_WAIT_UNTIL(thr, digitalRead(pin) == LOW);
// call the click callback
this->clicked();
}
PT_END(thr);
Then just call this thread repeatedly.
NOTE: when you have buttons connected, you would usually have pullup on the pin and have button connected between the pin and ground - so the pin is LOW when the button is down and high when it is not being pressed. This would certainly be the case on an arduino. So you would have to change the code above to wait for a negative pulse instead of a positive one. :)
Related
I have made this code on Arduino where the objective is to have the user type in a delay time into the serial monitor, and then the LED should be blink with that delay time. For example if I type in 1000 the LED should be turned on for 1 second then off for 1 second, then repeat.
My problem is that when the code has finished running once, it waits for a new user input, instead of continuing to blink. I think i have to take the Serial.parseInt out of the loop but i'm not sure how as every time I have tried to put it somewhere else the LED just lights up constantly.
Here is the code:
int ledPin = 13;
void setup() {
// put your setup code here, to run once:
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
Serial.print(" Enter delay time: ");
while (!Serial.available());
}
void loop() {
// put your main code here, to run repeatedly
int delayTime = Serial.parseInt();
digitalWrite(ledPin, HIGH);
delay(delayTime);
digitalWrite(ledPin, LOW);
delay(delayTime);
}
Serial.parseInt is a blocking function. That means it waits for valid serial input until it times out. Because of this, any other action in loop has to wait too. Reading user input in setup works only once though, so it never asks the user for input again.
To avoid this, you'll have to check the serial buffer, and then read each byte individually, while also doing the LED blinking in the main loop.
Another thing to avoid now, is the use of the delay function, because it also hangs the entire main loop (including the serial readings) for the given parameter time. You can still blink the LED by using timestamp intervals.
For a nice example of a non-blocking serial read, we can use this sample from the Arduino docs. Additionally, for another nice example of an LED-blinking sketch without using delay, we can use the BlinkWithoutDelay sample from the Arduino docs too.
String inString = "";
unsigned long previousMillis = 0;
int delayTime = 0;
int ledState = LOW;
int ledPin = 13;
void nonBlockingSerialReadDelayTime() {
while (Serial.available() > 0) {
int inChar = Serial.read();
if (isDigit(inChar)) {
// convert the incoming byte to a char and add it to the string
inString += (char)inChar;
}
// if you get a newline (user pressed ENTER on the serial console)
if (inChar == '\n') {
// set our new delay time
delayTime = inString.toInt();
// clear the string for new input
inString = "";
// ask user again
Serial.print(" Enter delay time: ");
}
}
}
void blinkLED() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= delayTime) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
// set the LED with the ledState of the variable
digitalWrite(ledPin, ledState);
}
}
void setup() {
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
while (!Serial.available());
Serial.print(" Enter delay time: ");
}
void loop() {
nonBlockingSerialReadDelayTime();
blinkLED();
}
Simply read the delay time in your setup befor you enter loop
int ledPin = 13;
int delayTime = 0;
void setup() {
// put your setup code here, to run once:
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
Serial.print(" Enter delay time: ");
while (!Serial.available());
delayTime = Serial.parseInt();
}
void loop() {
// put your main code here, to run repeatedly
digitalWrite(ledPin, HIGH);
delay(delayTime);
digitalWrite(ledPin, LOW);
delay(delayTime);
}
Sure Serial.parseInt() is blocking, but you can combine it with Serial.available()
const int ledPin = 13;
int delayTime = 1000;
void setup() {
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
Serial.print(" Enter delay time: ");
}
void loop() {
digitalWrite(ledPin, HIGH);
delay(delayTime);
digitalWrite(ledPin, LOW);
delay(delayTime);
if (Serial.available()) {
int temp = Serial.parseInt();
if (temp > 0) delayTime = temp;
Serial.print(" Enter delay time: ");
}
}
Of course this approach does not allow to break into a very slow blink cycle immediately, but that's a different issue.
In a basic variable and Button setup on an Arduino software, the idea is that if you press a button, you gain one on a variable. Theoretically, holding the button shouldn't cause the variable to increase more than one until the release and repressing of the button. However, I could not make this happen. How is this supposed to be done?
I tried using an else around a delay (because the adding one to a variable code was inside an "if" statement) so it would not delay unless the button and released and would not count multiple button inputs, but this resulted in a larger increase when a button was clicked rather than just a one increase.
const int buttonPin = 8;
int number = 0;
int numbertwo = 0;
int buttonState = 0;
void setup()
{
pinMode(buttonPin, INPUT);
}
void loop()
{
buttonState = digitalRead(buttonPin);
if (buttonState == LOW){
numbertwo = ++number;
delay(100);
}
(Can use any method to determine variable, like on an LCD)
As mentioned above, I expected the variable to only increase by one when a button is pressed and not to continue increasing until the button is released and re-pressed, but what actually happened was the variable kept increasing in value as the button was held.
I'm assuming you want the variable "number" increase by 1 per press. I think you are looking for something like this:
const int buttonPin = 8;
int number = 0;
int buttonState = 0;
void setup() {
pinMode(buttonPin, INPUT);
}
void loop()
{
buttonState = digitalRead(buttonPin);
if (buttonState == LOW){
++number;
// This loop is to make sure not to count up until button is released
do {
delay(5);
buttonState = digitalRead(buttonPin);
} while (buttonState == LOW);
}
}
If you don't like the loop based implementation, here's a state based implementation for you:
const int buttonPin = 8;
int number = 0;
int buttonState = 0;
int prevState = -1;
void setup() {
pinMode(buttonPin, INPUT);
prevState = -1;
}
void loop()
{
buttonState = digitalRead(buttonPin);
if (buttonState == LOW && prevState != buttonState){
++number;
prevState = buttonState;
}
delay(5);
}
I have a program that moves a stepper motor to the right, left and have a stop button that stops the motor. In one part of my program, a motor gradually lowers a speed and stops after a certain period of time iv.
The problem is that in this part of a program (when a motor gradually lowers a speed and then stops) I can’t stop the motor upon pressing a stop button. I understand that I need to break a while loop somehow, but using a break statement doesn't wort for me.
Do you have some ideas?
Here is my function:
/* --- STEPPER MOTOR ---*/
const int motor_step = 3;
const int motor_dir = 4;
int stepSpeed = 0;
int stepMaxSpeed = 1000;
int fadeAmount = 100;
int fadeDelay = 10;
/* ---- STOP BUTTON ---- */
int buttonStop = 5;
int stateStop=0;
void setup() {
.
.
.
stateStop = digitalRead(buttonStop);
}
void loop () {
.
.
.
myfunc();
}
void myfunc() {
if(stateStop == HIGH) {noTone(motor_step); stepSpeed = 0;}
elapsedMillis te;
unsigned int iv = 1500;
while (te < iv) {
if(stepSpeed == stepMaxSpeed) {
stepSpeed = stepSpeed+0;
tone(motor_step,stepSpeed);
digitalWrite(motor_dir,HIGH);
}
else {
stepSpeed = stepSpeed + fadeAmount;
tone(motor_step,stepSpeed);
digitalWrite(motor_dir,HIGH);
delay(fadeDelay);
}
if(stateStop == HIGH) { stepSpeed = 0; break;}
}
if(stepSpeed == stepMaxSpeed) {
while(stepSpeed>0){
stepSpeed = stepSpeed-fadeAmount;
tone(motor_step,stepSpeed);
digitalWrite(motor_dir,HIGH);
delay(fadeDelay);
if(stateStop == HIGH) { stepSpeed = 0; break;}
}
}
stepSpeed = 0;
noTone(motor_step);
digitalWrite(enable,LOW); // enable changed from HIGH
}
Your break condition does never trigger as stateStop is never being updated inside your while loop. How is your program supposed to know? It's busy running the loop and does not care about anything outside it's scope.
Check the button state inside the while loops or use interrupts
I'm having trouble displaying an image and playing audio using p5 when my arduino red led goes off. My arduino works, I just can't figure out how to get an image to pop up and audio to play when the red led goes off. My project is basically a motion sensor using an ultrasonic sensor, if that helps.
Thank you for your time.
Here's my sketch.js code:
var serial; // variable to hold an instance of the serialport library
var portName = '/dev/cu.usbmodem1421'; // fill in your serial port name here
var synth;
function preload() {
alert = loadImage('alert.jpeg');
}
function setup() {
createCanvas(1920,1080);
serial = new p5.SerialPort(); // make a new instance of the serialport
library
serial.on('list', printList); // set a callback function for the serialport
list event
serial.on('connected', serverConnected); // callback for connecting to the
server
serial.on('open', portOpen); // callback for the port opening
serial.on('data', serialEvent); // callback for when new data arrives
serial.on('error', serialError); // callback for errors
serial.on('close', portClose); // callback for the port closing
serial.list(); // list the serial ports
serial.open(portName); // open a serial port
var synth = new Tone.Synth().toMaster();
synth.triggerAttackRelease(440, 2);
var player = new Tone.Player("IntruderAlert.mp4").toMaster();
//play as soon as the buffer is loaded
player.autostart = true;
player.playbackRate = 0.8;
}
function serverConnected() {
println('connected to server.');
}
function portOpen() {
println('the serial port opened.')
}
function serialEvent() {
}
function serialError(err) {
println('Something went wrong with the serial port. ' + err);
}
function portClose() {
println('The serial port closed.');
}
Here's my .ino code:
#define trigPin 6<br>#define echoPin 7
#define GreenLED 11
#define YellowLED 10
#define RedLED 9
#define buzzer 3
int sound = 500;
void setup() {
Serial.begin (9600);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
pinMode(GreenLED, OUTPUT);
pinMode(YellowLED, OUTPUT);
pinMode(RedLED, OUTPUT);
pinMode(buzzer, OUTPUT);
}
void loop() {
long duration, distance;
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = (duration/5) / 29.1;
if (distance < 50) {
digitalWrite(GreenLED, HIGH);
}
else {
digitalWrite(GreenLED, LOW);
}
if (distance < 20) {
digitalWrite(YellowLED, HIGH);
}
else {
digitalWrite(YellowLED,LOW);
}
if (distance < 5) {
digitalWrite(RedLED, HIGH);
sound = 1000;
}
else {
digitalWrite(RedLED,LOW);
}
if (distance > 5 || distance <= 0){
Serial.println("Out of range");
noTone(buzzer);
}
else {
Serial.print(distance);
Serial.println(" cm");
tone(buzzer, sound);
}
delay(300);
}
You're going to have to break your problem down into smaller steps and then approach those steps one at a time.
Can you create a sketch that just shows an image? Forget about the Arduino for a second, and just display an image. Now can you display an image when the user clicks on the screen?
Separately from that, can you write Arduino code that simply sends a message to a sketch when the red light goes off?
When you get those working independently, then it'll be much easier to think about combining them into a single application that combines both ideas.
If you get stuck, please post a MCVE of the specific step that you're stuck on, and we'll go from there. Good luck.
Thanks in advance for your help.
Scenario Overview
In the real world, I am using one button to open two mechanical valves, but one of those valves should close after a period of time that we will hard code into the sketch, and the other valve stays open for as long as the button is pushed. For proof of concept, I am lighting two LEDs as stand-ins for the valves.
Pseudocode
If Button One is pressed, Valve One should Open, and Valve Two should also Open for 200ms then Close.
Initial Solution
Within the main loop, I look for the button to be pushed as part of an if statement. When that condition is passed, I used a while loop and timer to keep "valve2" open until the time is up. LEDs work, and all is superficially great. However...
The Issue
When my partner starts putting the actual mechanicals together, valve2 doesn't open because the while loop is cycling so quickly that the voltage required to initiate the opening of the valve is not high enough.
My Question
Is it possible to isolate (without using delays) the loop & evaluation of the timer condition from the main loop in order to allow full power to be sent to the valve mechanism (or LED in this case)? Or am I overthinking this whole thing (likely the case)?
The Code
const int button1 = 2; //Pin for switch 1
const int button2 = 3; //Pin for switch 2
const int valve1 = 12; //Pin for relay 1
const int valve2 = 13; //Pin for relay 2
// variables will change:
int state1 = 0; // variable for reading the pushbutton status
int state2 = 0; // variable for reading the pushbutton status
//THIS IS THE TIME IN MILLISECONDS FOR valve2 WHEN button1 IS DEPRESSED
int valve2time = 200;
void setup() {
//switches
pinMode(button1,INPUT); //Set button1 as input
pinMode(button2, INPUT); //Set button2 as input
//relays
pinMode(valve1, OUTPUT); //Set valve1 as output
pinMode(valve2, OUTPUT); //Set valve2 as output
Serial.begin(9600);
}
void loop(){
state1 = digitalRead(button1); //state1 returns the state of button1, up or down.
state2 = digitalRead(button2); //state2 returns the state of button2, up or down.
int duration = switchTime(); //Create variable to capture duration of switch press
if (state1 == LOW && state2 == LOW){ //if no buttons are pressed
digitalWrite(valve1,LOW); //make sure valve1 is off
digitalWrite(valve2,LOW); //make sure valve2 is off
}
else if (state1 == HIGH && state2 == LOW) { //if JUST button one is pressed
digitalWrite(valve1,HIGH); //turn on valve1
while (duration <= valve2time){ //as long as the timer is below or = to what we defined up top....
digitalWrite(valve2,HIGH); //...Turn on valve2...
break; //...Then stop the while loop...
}
digitalWrite(valve2,LOW); //...and finally turn off valve2
}
else if (state2 == HIGH){ //final condition, if button two is pressed
digitalWrite(valve1,HIGH); //turn on valve1
digitalWrite(valve2,HIGH); //turn on valve2
}
}
//return the time in ms that the switch has been pressed (LOW)
long switchTime(){
//these variables are static
static unsigned long startTime = 0; //the time the switch state was first detected
static boolean state; //the current state of the switch
if(digitalRead(button1) != state){ //check to see if the switch has changed state
state = ! state; //yes, invert the state
startTime = millis(); //store the time
}
if(state == HIGH){
return millis() - startTime; //switch pushed, return time in ms
}
else{
return 0; //return 0 if the switch is not pushed (in the HIGH state)
}
}
UPDATE: The working Code
//button pins
const int BUTTON1_PIN = 2;
const int BUTTON2_PIN = 3;
const int VALVE1_PIN = 0; //mml for tiny
const int VALVE2_PIN = 1; //mml for tiny
// IO Channels - Used to simulate arduino IO
boolean inputChannels[] = {LOW, LOW}; // digital input channels "Button1" and "Button2"
boolean outputChannels[] = {LOW, LOW}; // digital output channels "Valve1" and "Valve2"
// =============================================================================================================
// You can probably ignore everything above this line
// State machine variables
const int STATE_CLOSED = 0;
const int STATE_BUTTON1_PRESSED = 1;
const int STATE_BUTTON1_RELEASED = 2;
const int STATE_BUTTON2_PRESSED = 3;
const int STATE_BUTTON2_RELEASED = 4;
int currentState = 0;
int lastState = 0;
// button debounce time in ms
unsigned long BUTTON_DEBOUNCE = 200;
unsigned long BUTTON1_PRESSED_VALVE2_FLASH = 350;
unsigned long BUTTON1_RELEASE_VALVE2_FLASH = 1000;
// state tracking arrays
boolean buttonState[] = {LOW, LOW};
boolean buttonDebounce[] = {LOW, LOW};
unsigned long buttonTimers[] = {0, 0};
unsigned long valveTimers[] = {0, 0};
void setup(){
pinMode(BUTTON1_PIN, INPUT);
digitalWrite(BUTTON1_PIN, HIGH); //MML
pinMode(BUTTON2_PIN, INPUT);
digitalWrite(BUTTON2_PIN, HIGH); //MML
pinMode(VALVE1_PIN, OUTPUT);
pinMode(VALVE2_PIN, OUTPUT);
}
/**
* Main control loop
*/
void loop() {
switch (currentState) {
case STATE_CLOSED:
handleClosedState();
lastState = STATE_CLOSED;
break;
case STATE_BUTTON1_PRESSED:
handleButton1PressedState();
lastState = STATE_BUTTON1_PRESSED;
break;
case STATE_BUTTON1_RELEASED:
handleButton1ReleasedState();
lastState = STATE_BUTTON1_RELEASED;
break;
case STATE_BUTTON2_PRESSED:
handleButton2PressedState();
lastState = STATE_BUTTON2_PRESSED;
break;
case STATE_BUTTON2_RELEASED:
handleButton2ReleasedState();
lastState = STATE_BUTTON2_RELEASED;
break;
default:;
}
}
/**
* Handler method for STATE_CLOSED
*/
void handleClosedState() {
// ensure valves are closed
if (digitalRead(VALVE1_PIN) == HIGH) {
digitalWrite(VALVE1_PIN, LOW);
}
if (digitalRead(VALVE1_PIN) == HIGH) {
digitalWrite(VALVE2_PIN, LOW);
}
// wait for button1 press
if (LOW == debouncedDigitalRead(BUTTON1_PIN, BUTTON_DEBOUNCE)) {
buttonState[BUTTON1_PIN] = LOW;
currentState = STATE_BUTTON1_PRESSED;
}
}
/**
* Handler method for STATE_BUTTON1_PRESSED
*/
void handleButton1PressedState() {
// check for button1 release
if (HIGH == debouncedDigitalRead(BUTTON1_PIN, BUTTON_DEBOUNCE)) {
currentState = STATE_BUTTON1_RELEASED;
return;
}
// open valve1
if (digitalRead(VALVE1_PIN) == LOW) {
valveTimers[VALVE1_PIN] = millis();
digitalWrite(VALVE1_PIN, HIGH);
}
// on state change open valve2
if (lastState != currentState) {
valveTimers[VALVE2_PIN] = millis();
digitalWrite(VALVE2_PIN, HIGH);
}
// and close it after 200 ms
else if ((millis() - valveTimers[VALVE2_PIN]) > BUTTON1_PRESSED_VALVE2_FLASH && digitalRead(VALVE2_PIN) == HIGH) {
digitalWrite(VALVE2_PIN, LOW);
}
// check for button2 press
if (LOW == debouncedDigitalRead(BUTTON2_PIN, BUTTON_DEBOUNCE)) {
currentState = STATE_BUTTON2_PRESSED;
}
}
/**
* Handler method for STATE_BUTTON1_RELEASED
*/
void handleButton1ReleasedState() {
// open valve2
if (lastState != currentState) {
valveTimers[VALVE2_PIN] = millis();
digitalWrite(VALVE2_PIN, HIGH);
digitalWrite(VALVE1_PIN, LOW);
}
// and close valve2 after 1000ms
else if ((millis() - valveTimers[VALVE2_PIN] > BUTTON1_RELEASE_VALVE2_FLASH)) {
digitalWrite(VALVE2_PIN, LOW);
currentState = STATE_CLOSED;
}
}
/**
* Handler method for STATE_BUTTON2_PRESSED
*/
void handleButton2PressedState() {
// open valve2
if (digitalRead(VALVE2_PIN) == LOW){
digitalWrite(VALVE2_PIN, HIGH);
digitalWrite(VALVE1_PIN, HIGH);
}
// check for button1 release
if (HIGH == debouncedDigitalRead(BUTTON1_PIN, BUTTON_DEBOUNCE)) {
currentState = STATE_BUTTON1_RELEASED;
}
// check for button2 release
else if (HIGH == debouncedDigitalRead(BUTTON2_PIN, BUTTON_DEBOUNCE)) {
currentState = STATE_BUTTON2_RELEASED;
}
}
/**
* Handler method for STATE_BUTTON2_PRESSED
*/
void handleButton2ReleasedState() {
// open valve2
if (digitalRead(VALVE2_PIN) == HIGH){
digitalWrite(VALVE2_PIN, LOW);
digitalWrite(VALVE1_PIN, HIGH);
}
// check for button1 release
if (HIGH == debouncedDigitalRead(BUTTON1_PIN, BUTTON_DEBOUNCE)) {
currentState = STATE_BUTTON1_RELEASED;
}
// check for button2 press
else if (LOW == debouncedDigitalRead(BUTTON2_PIN, BUTTON_DEBOUNCE)) {
currentState = STATE_BUTTON2_PRESSED;
}
}
/**
* Utility for debouncing input channels
* #param channel
* #param debounce
* #return
*/
boolean debouncedDigitalRead(int channel, unsigned long debounce) {
int input = digitalRead(channel);
if (input != buttonState[channel] && HIGH == buttonDebounce[channel]) {
buttonTimers[channel] = millis();
buttonDebounce[channel] = LOW;
}
if ((millis() - buttonTimers[channel]) > debounce) {
buttonState[channel] = input;
buttonDebounce[channel] = HIGH;
}
return buttonState[channel];
}
In order for the code to simultaneously a) keep looping to check the buttons, and b) achieve the desired behavior for valve2, you need a software state machine that keeps track of what valve2 is doing. In the code below, I renamed your state1 and state2 variables, so that I could introduce a new state variable that controls valve2.
The state variable is normally in the idle state.
When button1 is pressed
valve2 is turned on
a timestamp is taken
the state is changed to active
After a 200 msec delay
valve2 is turned off
the state is changed to done
The state will stay done until either button1 is released or button2 is pressed, since either of those actions resets the state to idle.
Here's what the code looks like
void loop()
{
int state = 0; //variable to keep track of valve2: 0=idle 1=active 2=done
unsigned long start; //variable to keep track of when valve2 was turned on
boolean pressed1 = (digitalRead(button1) == HIGH); //pressed1 is true if button1 is pressed
boolean pressed2 = (digitalRead(button2) == HIGH); //pressed2 is true if button2 is pressed
if ( !pressed1 && !pressed2 ) //if no buttons are pressed
{
digitalWrite(valve1,LOW); //make sure valve1 is off
digitalWrite(valve2,LOW); //make sure valve2 is off
state = 0; //clear valve2 state
}
else if ( pressed2 ) //if button2 is pressed
{
digitalWrite(valve1,HIGH); //turn on valve1
digitalWrite(valve2,HIGH); //turn on valve2
state = 0; //clear valve2 state
}
else //button1 is pressed
{
digitalWrite(valve1,HIGH); //turn on valve1
if ( state == 0 ) //if valve2 is idle
{
digitalWrite(valve2,HIGH); //turn on valve2
state = 1; //valve2 is active
start = millis(); //capture the start time
}
else if ( state == 1 ) //if valve2 is active
{
if ( millis() - start > 200 ) //has it been 200ms?
{
digitalWrite(valve2,LOW); //turn valve2 is off
state = 2; //valve2 is done
}
}
}
}