I am using a solenoid and two photoelectric optical eyes to help automate a reservoir for RODI water. I am having trouble figuring out the best way to program a certain command (very new to Arduino programming). I am trying to have the program open the solenoid to run the water into the tank - this is triggered by the bottom optical eye showing LOW (no water). Once the water fills the tank the upper optical high triggers the solenoid to close stopping the flow of water. I would like to have it not continually topping itself off based off the upper optical eye. I am trying to have the solenoid stay closed (once the tank is full) and not turn back on until the water level has reached the bottom optical eye (trying to replicate an "until" command. Any assistance or recommendations would be very helpful - here is my current attempt at the code:
int ATOsolenoid = 3; //This is the output pin on the Arduino we are using
int solenoidPin = 4; //This is the output pin on the Arduino we are using
int lowEye = 7;
int highEye = 8;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(ATOsolenoid, OUTPUT); //Sets the pin as an output
pinMode(solenoidPin, OUTPUT); //Sets the pin as an output
pinMode(lowEye, INPUT);
pinMode(highEye, INPUT);
}
void loop() {
// put your main code here, to run repeatedly:
digitalRead(lowEye);
digitalRead(highEye);
//ATO Solenoid
if (digitalRead(lowEye) == LOW && digitalRead(highEye) == LOW) {
digitalWrite(ATOsolenoid, HIGH); //Switch Solenoid ON
} else if (digitalRead(lowEye) == HIGH && digitalRead(highEye) == LOW) {
digitalWrite(ATOsolenoid, HIGH); //Switch Solenoid ON
}
while (digitalRead(lowEye) == HIGH) {
digitalWrite(ATOsolenoid, LOW);
}
if (digitalRead(lowEye) == HIGH && digitalRead(highEye) == HIGH) {
digitalWrite(ATOsolenoid, LOW); //Switch Solenoid OFF
}
}
I realize the while command is looping continuously and pretty much negates any of my if statements trying to control the solenoid. Thanks in advance!
My approach would be a "state machine", in which a variable describes the current state of the tank (filling, emptying, no action, etc.). That way your logical decisions can have more options.
Take a look at the State Change Detection example in the Arduino IDE (Files>Examples>02.Digital>...)
do ... until() and while() {...} are blocking, and since loop() loops, should be avoided.
I had the same problem with my irrigation tank filling control. Filled from the top, the waves would trigger the top float off and on.
The solution is to add a Boolean to the logic that is set to TRUE when the top limit is first discovered. All of your test for full must be changed to include the Boolean being false for the water to continue.
Only set the Boolean to FALSE when you discover the reservoir is empty and needs to be filled.
Thank you both for the suggestions - I will look into those. The idea of the Boolean logic for the reservoir makes sense to me, now it's just figuring out how to implement that into the programming. In the meantime I have been playing with the code and seemed to find a way to write it that meets my needs? Here is the slight change:
int ATOsolenoid = 3; //This is the output pin on the Arduino we are using
int solenoidPin = 4; //This is the output pin on the Arduino we are using
int lowEye = 7;
int highEye = 8;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(ATOsolenoid, OUTPUT); //Sets the pin as an output
pinMode(solenoidPin, OUTPUT); //Sets the pin as an output
pinMode(lowEye, INPUT);
pinMode(highEye, INPUT);
}
void loop() {
// put your main code here, to run repeatedly:
digitalRead(lowEye);
digitalRead(highEye);
//ATO Solenoid
if (digitalRead(lowEye) == LOW && digitalRead(highEye) == LOW) {
digitalWrite(ATOsolenoid, HIGH); //Switch Solenoid ON
} else if (digitalRead(lowEye) == HIGH && digitalRead(highEye) == LOW) {
digitalWrite(ATOsolenoid, HIGH); //Switch Solenoid ON
}
if (digitalRead(lowEye) == HIGH && digitalRead(highEye) == HIGH) {
digitalWrite(ATOsolenoid, LOW); //Switch Solenoid OFF
while (digitalRead(lowEye) == HIGH) {
digitalWrite(ATOsolenoid, LOW);
}}}
From dry testing (blowing through the solenoid) this seems to have accomplished what I wanted - does this seem logical or am I missing something obviously wrong in the programming?
it works. Though the only thing that could make the code come outside this while-loop
is that the IO-pin lowEye becomes LOW
or you press the reset-button of the microcontroller
or switch off / on the powersupply of the microcontroller
This is what is called blocking. The while-loop is blocking all other code-execution
If something else shall happen you would have to write it into the while-loop
and very often then you have to write that code in two places the "normal" place and additional in such while-loops
very ugly to maintain.
This is the reason why writing oce non-blocking is much better and much more flxeible
and easier to maintain once you have the basic code working
This is very simple, the hardest part is getting the sense of the sensors correct.
void loop() {
// fix these if I got them wrong
bool waterIsLow = digitalRead(lowEye) == LOW;
bool waterIsHigh = digitalRead(highEye) == HIGH;
// then all you need to do is
if (waterIsLow) {
digitalWrite(ATOsolenoid, HIGH);
}
if (waterIsHigh) {
digitalWrite(ATOsolenoid, LOW);
}
}
Run your finger over that pretending to execute the code line by line, see if it works for you. I could bot test this from uber the umbrella.
The first if statement turns the water on when the tank is low. It is on until the tank looks full, and switches off, staying off until the water is low again.
You could check the two Eye bool values for impossible conditions, like water too low and too high at once, and take appropriate action, here I would say a warning that something is amiss.
both eyes are OFF when then tank is filled.
the upper eye becomes active (ON) when the water drops below it.
the lower eye becomes active when the tank needs to be refilled.
the solenoid can be turned off when the upper eye is no longer active (OFF)
int ATOsolenoid = 3;
int lowEye = 7;
int highEye = 8;
enum { ATO_OPEN = HIGH, ATO_CLOSE = LOW }; // per alto777
enum { EYE_ON = HIGH, EYE_OFF = LOW };
// -----------------------------------------------------------------------------
void loop ()
{
if (EYE_ON == digitalRead(lowEye)) // low
digitalWrite (ATOsolenoid, ATO_OPEN);
else if (EYE_OFF == digitalRead(highEye))
digitalWrite (ATOsolenoid, ATO_CLOSE);
}
void setup () {
Serial.begin (9600);
pinMode (ATOsolenoid, OUTPUT); //Sets the pin as an output
pinMode (lowEye, INPUT);
pinMode (highEye, INPUT);
}
Which means the solenoid is active? Is activating the solenoid closing or opening a valve?
Which means the sensor is blocked by the fluid, or is that even how they work? Is HIGH a blocked sensor, and is that the fluid blocking? Or is the sensor logic upside down, like it was a normally closed float switch or what?
I'll apply my own advice and improve my version:
if (waterIsLow) {
openTheValve();
}
if (waterIsHigh) {
closeTheValve();
}
Nothing in the logic about the digital reading and writing. Just something that is, in fact, made clear.
Thanks everyone for the input. In my case this blocking works very well for what I intend to use the setup for. I realize it may not be the most sufficient programming but for now I am content. Following up on this I have tried adding a second solenoid that changes state (open/closed) dependent on the state of the ATOsolenoid. When adding this into the code it seems to not reference the ATOsolenoid state and even negates the previously working code.
int ATOsolenoid = 3; //This is the output pin on the Arduino we are using
int FlushSolenoid = 4; //This is the output pin on the Arduino we are using
int lowEye = 7;
int highEye = 8;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(ATOsolenoid, OUTPUT); //Sets the pin as an output
pinMode(FlushSolenoid, OUTPUT); //Sets the pin as an output
pinMode(lowEye, INPUT);
pinMode(highEye, INPUT);
}
void loop() {
// put your main code here, to run repeatedly:
digitalRead(lowEye);
digitalRead(highEye);
digitalRead(ATOsolenoid);
//ATO Solenoid
if (digitalRead(lowEye) == LOW && digitalRead(highEye) == LOW) {
digitalWrite(ATOsolenoid, HIGH); //Switch Solenoid ON
} else if (digitalRead(lowEye) == HIGH && digitalRead(highEye) == LOW) {
digitalWrite(ATOsolenoid, HIGH); //Switch Solenoid ON
}
if (digitalRead(lowEye) == HIGH && digitalRead(highEye) == HIGH) {
digitalWrite(ATOsolenoid, LOW); //Switch Solenoid OFF
while (digitalRead(lowEye) == HIGH) {
digitalWrite(ATOsolenoid, LOW);
}
}
//Flush Solenoid
if (digitalRead(ATOsolenoid) == HIGH) {
digitalWrite(FlushSolenoid, HIGH); //Switch Solenoid ON
delay(20000); //On for 20 Seconds
digitalWrite(FlushSolenoid, LOW); //Switch Solenoid OFF
delay(3600000); //Off for 1 Hour
} else if (digitalRead(ATOsolenoid) == LOW) {
digitalWrite(FlushSolenoid, LOW);
}}
I want to be sure you understand that a one hour delay() means your code sits there, blind to all sensors and unable to control any solenoids or pumps and stuff. Literally nothing else happens, not at least to advance your process or algorithm for turning stuff on and off.
This logic is easily done with zero use of the delay() function.
For making one solenoid follow another, the below is entirely and exactly correct, and should make the FlushSolenoid output pin go HIGH if the ATOsolenoid pin has been previously set HIGH:
if (digitalRead(ATOsolenoid) == HIGH) {
digitalWrite(FlushSolenoid, HIGH);
//...
And same for the LOW reading being written to the other solenoid in the else/if .
TBH it is this exact thing that easily confuses me. That's why there is value in getting the inputs and outputs isolated so that there is only one place to fix exactly this kind of upside down or backwards mistake.
Adding some printing is nice, too - unambiguous:
void closeTheValve()
{
Serial.println("Closing the valve, boss!");
digitalWrite(ATOsolenoid, HIGH);
}
So when the valve opens when the clearly expressed (printed) intent is that it be instead closing, it doesn't take the greatest programmer in the world to find and fix the flaw.
The printing can be done so it happens when you need it, and can be simply turned off when you want the sketch to shut up. I usually don't bother, so lotsa my projects are (or would be if the output wan't just going in the bit bucket) quite chatty.
I am looking for an alternative way to accomplish my goal and was wondering if anyone has an ideas. I am looking to turn on a solenoid for 20 seconds every hour. I cannot use the delay function as the on/off for the solenoid calls upon the HIGH/LOW state of another solenoid and using the delay stops the program I need to run constantly. As an example here is my current code:
int ATOsolenoid = 3; //This is the output pin on the Arduino we are using
int FlushSolenoid = 4; //This is the output pin on the Arduino we are using
int lowEye = 7;
int highEye = 8;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(ATOsolenoid, OUTPUT); //Sets the pin as an output
pinMode(FlushSolenoid, OUTPUT); //Sets the pin as an output
pinMode(lowEye, INPUT);
pinMode(highEye, INPUT);
}
void loop() {
digitalRead(lowEye);
digitalRead(highEye);
digitalRead(ATOsolenoid);
bool waterIsLow = digitalRead(lowEye) == LOW;
bool waterIsHigh = digitalRead(highEye) == HIGH;
if (waterIsLow) {
digitalWrite(ATOsolenoid, HIGH);
}
if (waterIsHigh) {
digitalWrite(ATOsolenoid, LOW);
}
//Flush Solenoid
if (digitalRead(ATOsolenoid) == HIGH) {
digitalWrite(FlushSolenoid, HIGH); //Switch Solenoid ON
delay(20000); //On for 20 Seconds
digitalWrite(FlushSolenoid, LOW); //Switch Solenoid OFF
delay(3600000); //Off for 1 Hour
} else if (digitalRead(ATOsolenoid) == LOW) {
digitalWrite(FlushSolenoid, LOW);
}
}
I have read through the blink without delay tutorial but am having a tough time adapting it to this situation given I want the solenoid to hold in an on state for 20s every hour as well as it calling upon another variable solenoid. Thanks in advance!
"Blink without Delay" is a good start. The only add on you need is to differentiate the on time from the off time. 20 seconds vs. 60*60-20 seconds.
imho the easiest way is to introduce new constants for intervalOn and intervalOff and modify the used interval everytime you change the state of the pin.
/*
Blink Without Delay - with different intervals
https://forum.arduino.cc/t/turn-on-solenoid-for-certain-duration-every-hour/1228168/4
by noiasca
*/
const uint8_t ledPin = LED_BUILTIN; // the number of the LED pin
const uint32_t intervalOn = 500UL; // interval at which to blink (milliseconds)
const uint32_t intervalOff = 3000UL;
uint32_t interval = intervalOn; // will be changed during runtime
uint32_t previousMillis = 0; // will store last time LED was updated
void timerBlink() {
uint32_t currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis; // save the last time you blinked the LED
// if the LED is off turn it on and vice-versa:
if (digitalRead(ledPin) == LOW) {
digitalWrite(ledPin, HIGH);
interval = intervalOn; // the interval for the next iteration
} else {
digitalWrite(ledPin, LOW);
interval = intervalOff; // the interval for the next iteration
}
}
}
void setup() {
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
}
void loop() {
timerBlink();
// do other things non-blocking here
}
additionally I like to put a closed functionality in a separate function. Hence the timerBlink().
When you describe the problem you had with the different times, one might be able to help you.
Imho you could implement a Finite State Machine with 3 States:
IDLE
FLUSH_ACTIVE
FLUSH_WAIT
check in each state the conditions and jump to the next state in your flow.
Something like:
So you want the flush sol to come on for 20 sec only when the ATOsolenoid first comes on? If the water goes LOW before the 1 hr timer expires, do you still want to wait to turn ATOsolenoid on?
What is the connection or logic or constraint between the two solenoids and whatever they do?
@gcjr's code runs two completely unrelated solenoid logic/timing sections.
@noiasca's state diagram doesn't make the connection obvious, at least m[not yet to me.
And your words
are ambiguous.
Try plain words again. Name the solenoid(s) when you mention them.
The flush solenoid should turn on for 20 seconds every time the fill solenoid finishes, the fill solenoid is turned off by the high eye and then then flush solenoid should run.
It see? The flush solenoid should run 20 seconds once an hour if the high eye says the tank is full.
Say what you want like that. Plain thorough and unambiguous. The code is no problem, understandable what you want is clear yet not.