Dishwasher control advice needed

Hello I would like some guidance with a project I am building. I am trying to program an Arduino to run my dishwasher as the control card on it has failed again. So I thought this would be a fun project. ;D

I made a start with righting a sketch using the RTC_DS3231 clock which worked but was getting to complicated and had problems. So I am starting again using delays and counts.

I think most of it I can do but have come across a hurdle from the start that I am not sure how to tackle. The program will need to run across a number of sequences (Drain washer, Fill, rinse, drain, fill, wash ETC). This should be ok for me to write but I also need to create a function for DOOR OPEN and if someone has operated the OnOff button which stop’s all wash but when put back (door closed) wash carries on from where it left off not from the beginning. This part is proving difficult and was why I dropped the clock idea as time does not stop.

I have got something working shown below but would like to know if this is a good way of doing this or a poor idea. I am not a programmer I only do this as a hobby so any help would be much appreciated. I have used the interrupt command which stopped the program when door open but when closed program started from the beginning again and not from when it stopped it.

I hope this all makes sense and i have not waffled on too much.

int buttonOnOff = 22;     //On/Off push button
int doorSwitch = 24;     //washer door switch
int drain = 25;          //drain pump
int progState;           //Used to say if wash is on or off
int prevOnOff;           //used for on off button control     
int door;                // Hold door state
int drainCount;          // use to count how long to drain washer
int programCount;        // used to count program running time

void setup() {
  Serial.begin(9600);
  pinMode(buttonOnOff, INPUT);
  digitalWrite(buttonOnOff, HIGH);     //set internal pullup resistor for input
  pinMode(doorSwitch, INPUT);
  digitalWrite(doorSwitch, HIGH);
  pinMode(drain, OUTPUT);
}

void loop() {
  Door();             // check door has not been opened if so then stop wash
  onoffCheck();       //check on off switch for program statuse
  while (progState == 1  && door == 1){         //if on status = 1 and door closed = 1 run wash
    drainFun(10);                               // call drain function pass 10 as the count for this drain time
    fillFun(10);                                // call fill function pass 10 for this count
    progState = 0;                              // end wash set program state to 0 to stop while loop
  }
}

// this function checks if button pushed and toggles progState variable from 1 to 0
void onoffCheck(){
  int readingOnOff = digitalRead(buttonOnOff);
  if (readingOnOff != prevOnOff){
    prevOnOff = readingOnOff;
    if (prevOnOff == 0){
    progState = !progState;
    }
  }
  
  while(progState == 0){                  // if progState = 0 wash off, turn all pumps and valves off and wait for next ON state
    Serial.println ("Dishwasher Off");
    digitalWrite(drain, LOW);
    delay(100);
    onoffCheck();
  }
}

// This function checks door, if it is opened turn all pumps off and what here till door is closed
void Door(){
   door = digitalRead(doorSwitch);
   while (door == 0){
    door = digitalRead(doorSwitch);
    Serial.println(door);
    Serial.print("Door Open");
    delay(100);
   } 
}

// this is the drain function
void drainFun(int a){
  while(drainCount < 10 && progState == 1){
    Door();           // if door is opened stop program
    onoffCheck();     // check to see if on off button has been pushed during wash. if so stop all pumps
    drainCount++;
    Serial.println("Startup Drain");
    Serial.println(drainCount);
    if(drainCount<a){
      digitalWrite(drain, HIGH);
    }
    delay(100);
    if (drainCount == a){
      digitalWrite(drain, LOW);
    }
  }
  drainCount = 0;
}

// fill function to be added
void fillFun(int a){
  Serial.println("HERE");
  delay(1000);
}

Don't use WHILE and don't use delay() because both make the program un-responsive. You should design the program so loop() repeats hundreds or thousands of times per second. Have a look at how millis() is used to manage timing without blocking in Several things at a time

If you want to be able to pause and continue then you need a variable that keeps track of the progress of the wash cycle. That way, whatever has happened, the program can check the variable and know what it is supposed to be doing. (I'm assuming, of course, that the Arduino is not switched off :slight_smile: )

And whatever you do DO NOT call the function onoffCheck() from within itself. That is what is known as a circular reference and your program will just vanish down the plughole. Circular references are for very experienced programmers - and even they can be bitten.

...R

Thanks Rob, I have not liked the millis() as I have found this difficult to use properly. I will have a look over your demo and see if i can implement this.

Looks like I have a lot more to learn.

steve8428:
I have not liked the millis() as I have found this difficult to use properly.

It is pretty much an essential skill for Arduino programming :slight_smile:

Using millis() is much the same as using your kitchen wall clock to time the cooking of a chicken. Using delay() is the equivalent of sitting watching the oven for 90 minutes - which you would not dream of doing. Instead you note the time when the chicken goes in the oven and periodically check the clock to see if the 90 minutes has elapsed.

...R

Thanks Robin, Thats a great way to look at it and i wish it was that easy to me. I have just written a count script below which counts with no delay but it stops at 66 count. Is there a reason for this.

int previousTime;
int programDelay = 500;
int count;

void setup() {
  Serial.begin(9600);
}

void loop() {
  countFun ();
}

void countFun (){
  if (millis() - previousTime == programDelay){
    previousTime = millis();
    count++;
    Serial.println(count);
    Serial.print("Previous Time = ");
    Serial.println(previousTime);
    Serial.print("Time Now = ");
    Serial.println(millis());
  }
}

Your datatype is too small. instead use:

unsigned long previousTime

Another thing to keep in mind - for various reasons, things could get started anywhere (there may already be water in the dishwasher), so it is a good plan to always run the drain cycle at the start.

This is not safe

if (millis() - previousTime == programDelay){

always use

if (millis() - previousTime >= programDelay){

because there is a risk that you do the test a microsecond after millis() has moved on to the next value.

And this

previousTime = millis();

will be slightly better if you do it like this

previousTime += programDelay;

because it avoids accumulating small errors due to the millis() value used for the update being different from the value used in the test. There is an extensive discussion of this in Several Things at a Time

...R

Thanks for all the comments, I've been away for the weekend so could not reply. i have been working on a test program and hope to post it this week if it works.

gpsmikey thanks - i was intending to run the drain function on starts. I want to try and get it running in the same sequence as the current one did.

Below is my try at running the program with no delays. At the moment I am only draining and filling just to get it working. It works great on my breadboard. It runs through the drian then fill when the ON button is pushed. If the door is opened the LED's (valves & pumps) go off and when door is closed again it will go to the state they was when door was opened.

But I am struggling on how to hold the timings when the door is opened so that it does not move onto the next flag straight away when the door is closed. At the moment when the door is opened millis is still running. so when closing the door its beyond the interval time and so the current process is stopped and the next one starts. I've tried a number of ways but with no success. :confused:

Script so far

//---General variables----
int flag = 0;              //used t control what is running, sequence flag
int count =0;              //used to turn items from on to off
byte prog_State = LOW;    // start and stop the wash

//-----Door variables reference D-----
int door = 24;
byte doorState;
unsigned long doorOpenTime = 0;
int doorCount = 0;

//-----On Off Button reference O------
const int O_Button = 22;
int O_buttonState;            // the current state of the input pin
int O_lastButtonState = LOW;  
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

//-----Drain variables------
int drainPump = 25;
unsigned long drain_Time = 0;
int drainStatus;

//----fill variables-----
int fillValve = 26;
unsigned long fill_Time = 0;
int fillStatus;

void setup() {
  Serial.begin(9600);
  pinMode(door, INPUT);
  digitalWrite(door, HIGH);
  pinMode(O_Button, INPUT);
  digitalWrite(O_Button, HIGH);
  pinMode(drainPump, OUTPUT);
  pinMode(fillValve, OUTPUT);
}

void loop() {
                //---program will do nothing until on button pushed prog_State = 1 and door closed = 1-----
  onOffFun();  // keep checking on/off button. if pushed start program if operated during program stopp all
  doorFun();   // keep checking door switch if open stop all program
               //----flag 0 start fromgram with an inital drain for about 5 min----
  drainFun (5000);
               //-----flag 1 drain stopped now fill the dishwasher----
  fillFun (5000);
              //----when flag = 100 wash has finnished so reset flag to 0 for next wash and prog_State to 0 to stop program.-----
  if (flag == 100){
    flag = 0;
    prog_State = 0;
    count = 0;
  }
  
//---- Serial test function used for testing only. When not in use will be commented out-----
 //Serialprint ();
}

void onOffFun (){
    // read the button status
    int reading = digitalRead(O_Button);
   
    if(reading != O_lastButtonState){  //meaning button has been pushed
      lastDebounceTime = millis();
     }
     if((millis() -  lastDebounceTime) >= debounceDelay){ // button held down long enought to count
      if(reading != O_buttonState){
        O_buttonState = reading;
        if (O_buttonState == LOW){
          prog_State = ! prog_State;  //change prog_State from 1 to 0 or vice versa
      }
     }
  }
  O_lastButtonState = reading;
}

//----Door function stop program if door opened----
void doorFun(){

   int reading = digitalRead(door);
   if(reading == 0){ //door open when program started so stop all
    doorState = 0;
      //---turn it all off---
      digitalWrite(drainPump, LOW);
      digitalWrite(fillValve, LOW);
   }  
   if(reading == 1){   // door closed
    doorState = 1;
//----reinstate all settings-----
    digitalWrite(drainPump, drainStatus);
    digitalWrite(fillValve, fillStatus);
   }
}

//----Drain function when called will run the drain pump for the interval time sent to it-----
void drainFun(int interval){
  if(((millis() - drain_Time) >= interval) && flag == 0 && doorState == 1 && prog_State == 1){
      drain_Time = millis();
      count++;
      if (count == 1){
        digitalWrite(drainPump, HIGH);
        drainStatus = digitalRead(drainPump);
      }
      if (count == 2){
        flag = 1;  //stop this process
        digitalWrite(drainPump, LOW);
        drainStatus = digitalRead(drainPump);
        count = 0; //reset counter
      } 
   }
 }      

//--- Fill function will fill the washer for the time the interval was sent
 void fillFun(int interval){
  
  if(((millis() - fill_Time) >= interval) && flag == 1 && doorState == 1 && prog_State == 1){
    fill_Time = millis();
    count++;
    if (count == 1){
      digitalWrite(fillValve, HIGH);
      fillStatus = digitalRead(fillValve);
    }
    if (count == 2){
      flag = 100;
      digitalWrite(fillValve, LOW);
      fillStatus = digitalRead(fillValve);
      count = 0;
    }
  }
 }

 void Serialprint (){
  Serial.println("***************************");
  Serial.print("Door State = ");
  Serial.println(doorState);
  Serial.print("ProgState = ");
  Serial.println(prog_State);
  Serial.print("Flag = ");
  Serial.println(flag);
  Serial.print("Drian Pump state = ");
  int drain = digitalRead(drainPump);
  Serial.println(drain);
  Serial.print("Fill Valve = ");
  int fill = digitalRead(fillValve);
  Serial.println(fill);
  Serial.print("count = ");
  Serial.println(count);
  
 }

steve8428:
At the moment when the door is opened millis is still running. so when closing the door its beyond the interval time

I guess you need to save the value of millis() when the door opens and when it closes so you can calculate how much to add to the interval

Or maybe a smarter way would be to calculate the time still to run when the door is opened and use that for the interval from the value of millis() when the door is closed. It may actually be easier to have a variable called millisStillToRun that is calculated every time you check the time with if (millis() - ..... I have a rough notion in my head that you could use the same variable for millisStillToRun for every action so that the opening and closing of the door would always use the same code.

Of course all this assumes that the Arduino remains powered up all the time.

...R

Apologies if any of this has already been covered.

Each interval of the wash cycle has a time value: (fill 3 min, pre-rinse 5 min, drain 3 min, fill 3 min, wash 25 min, drain 3 min, fill 3 min, 1st rinse 5 min, drain3 min, fill 3 min, 2nd rinse 5 min, drain 3 min, dry 30 min.
If you open the door during any of these you'll want to resume where you left off in that interval.

Don't forget to open the dispenser a minute or two into the wash interval. Most dispensers use a solenoid, some use wax motors. If yours is a wax motor you'll need to allow time to heat the wax before removing power from the dispenser open event.

Dishwashers have to accommodate "off grid" water pressure and city water pressure. The fill event duration has to be sufficient to adequately fill if water pressure is low. There is a float switch in series with the water valve to stop the flow if you have sufficient pressure to overfill during the fill event duration. Don't overlook the float switch!

The drying event: Most dishwashers have a thermal limit switch attached to the bottom of the tub. Some have thermistors. If you have a thermal limit make sure to keep it in series with the element. If you have a thermistor you need to accommodate that in your design too. These are important if you don't want to melt your stuff.

Washing: Look at any dishwasher installation manual and it will state prominently that the water supply temperature MUST be no less than 120° or your dishes will never ever come clean. The reality is nobody has 120° water temp to their dishwasher. The heating element runs during the wash cycle to get the temp above 120°. If you want to run a sanitary cycle you have to halt the wash cycle long enough to raise the water temp above 140°. As long as you are rolling your own, it would be a nice feature to halt the wash cycle until the temp reaches 120° too, but that's me.

Does your dishwasher have a turbidity sensor?

steve8428:
But I am struggling on how to hold the timings when the door is opened so that it does not move onto the next flag straight away when the door is closed. At the moment when the door is opened millis is still running. so when closing the door its beyond the interval time and so the current process is stopped and the next one starts. I've tried a number of ways but with no success.

Offered without warranty. This is still a work in progress but, it does illustrate a 'retentive' timer - that is, one which holds its accumulated value when the timer run signal is removed. The action is in the case statement.

/* 6/18/17
  DESCRIPTION
  ====================
  Implementation of a PLC-type retentive timer. A retentive timer holds
  its value when the enable is removed. It requires a separate RESET
  signal to start again at zero. Includes 'done' and 'timer timing' bits.
  attempt 2.1 introduces timers as structures.
*/
//#include <elapsedMillis.h>

#define BUTTON_PIN 5  // to enable the timer
#define RESET_PIN 4   // reset the timer to zero
#define LED_PIN 13

// create a structure type called 'timer'

struct timer {
  unsigned long pre;
  unsigned long acc = 0;
  bool dn = 0;
  bool en = 0;
  bool tt = 0;
};
struct timer T1;  // create a TON instance of timer known as T1
struct timer T2;    // T2 is a TOF
struct timer T3;    // T3 is an RTO

unsigned long last = 0;  // record previous millis()
unsigned long change;
//
//-----------  declare function 'RESET'
//
//void Reset(*struct timer)
int reset;  // switch to reset the RTO

void setup() {

  // Setup the button with an internal pull-up :
  pinMode(BUTTON_PIN, INPUT);

  T1.pre = 2000;  // initialize T1
  T2.pre = 2500;  // and T2
  T3.pre = 5650;  // and T3 preset to 5.65 seconds

  //Setup the LED :
  pinMode(LED_PIN, OUTPUT);
  //
  Serial.begin(9600);
}
void loop() {
  Serial.println(" acc   dn  en  tt");
  Serial.print(T3.acc);
  Serial.print("    ");
  Serial.print(T3.dn);
  Serial.print("   ");
  Serial.print(T3.en);
  Serial.print("   ");
  Serial.println(T3.tt);
  if (!digitalRead(BUTTON_PIN)) {
    T3.en = false;  // disable timer 3
  }
  else {
    T3.en = true;  // enable timer 3
  }
  //
  //   --------------  timer 3 logic - RTO
  //

  switch (T3.en) {
    case 1:
      change = millis() - last;
      last = millis();
      T3.acc = T3.acc + change;
      T3.tt = 1;
      if (T3.acc >= T3.pre) {
        T3.acc = T3.pre;
        T3.tt = 0;
        T3.dn = 1;
      }
      break;
    case 0:   // timer not enabled
      last = millis();
      if (T3.acc >= T3.pre) T3.dn = 1;
      T3.tt = 0;
  }   // end of switch

  //
  //------- RTO reset
  //
  if (digitalRead(RESET_PIN) == 1) {
    T3.dn = false;  // disable timer 3
    T3.acc = 0;
  }
  delay(300);
}

robin - yes the arduino will always be on and brings up a question will the variables be ok with the holding and calculating millis() after a few days. I expect the value will get quiet large.

i have been working tonight on the door timing and may have a work around, will let you know. Basically record the time the door was opened. Then when the door closes take this time away from current millis and add this to the interval.

But what you mentioned is something I will look at later if this does not work.

warnmar10 - thanks for your input some replies below

Each interval of the wash cycle has a time value: (fill 3 min, pre-rinse 5 min, drain 3 min, fill 3 min, wash 25 min, drain 3 min, fill 3 min, 1st rinse 5 min, drain3 min, fill 3 min, 2nd rinse 5 min, drain 3 min, dry 30 min.
If you open the door during any of these you'll want to resume where you left off in that interval.

Yes you are right still got to program all this in. the door problem is where i am at the moment and exactly what I need to do

Got the dispenser sorted. at 240V solenoid opens it.

Water pressure is done by a rotating wheel that operates a micro switch so this can be measured to get water flow rate and work out how much is in the dishwasher. But seeing as this will only be connected to my supply its not something I will be doing straight away. I hope to add this later.

I have not looked into the dry event and have not got a clue yet how I will do this. This is if i really need it as the current manufacturers one was rubbish.

Water temperature, Thanks for the info. i did not realize the temperature was this high. I run my water supply from the hot water which is normally 70 deg by the evening. So I will have to work this in at some point.

As for turbidity sensor i have never heard of this. Will have to look this one up.

steve8428:
robin - yes the arduino will always be on and brings up a question will the variables be ok with the holding and calculating millis() after a few days. I expect the value will get quiet large.

millis() rolls over from 232 - 1 to 0 after about 49 days. Provided all the variables associated with timing are defined as unsigned long; and the interval itself is not longer than 49 days; and the time check uses subtraction then the timing will work forever.

However the Arduino microprocessor does not run at exactly 16MHz so the timing will vary compared to a proper clock. I don't think that will matter for a wash cycle. But if you do need to stay in sync with your kitchen clock then you will need a Real Time Clock (RTC) module.

...R

Well that's good to know and it does not need to be clock accurate so this should be ok

I am having some trouble with using the millis delay. I have a small sketch for testing. It calls two functions and I have put a count on then to see what is happening. I need the fill function to be longer than it currently is. At the current interval of 9800 I get a count of 4594, but if i increase this interval the count goes on for ever.

Also if i change the drain interval it changes the fill time, the count will be shorter or longer.

Therefore i cannot fill the washer and when changing intervals all the settings for the other functions will change.

Is this because i am sending the interval by the function. One option may be to use a counter to control how long things run for. A millis function could run the counter so i do not have a delay

Any help would be great

int fillValve = 13;
int drainPump = 14;
int flag = 0;
unsigned long fill_Time = 0;
int fillStatus;

unsigned long drain_Time = 0;
int drainTime;

unsigned long count;

void setup() {
  Serial.begin(9600);
  pinMode(fillValve, OUTPUT);
  Serial.println("Setup Completed");
}

void loop() {
  switch(flag){
      case 0:
        drainFun (6000); //flag 0
      break;
      case 1:
        fillFun (98000); //flag 1
      break;
  }  
}

void drainFun(int Draininterval){
  count++;
  Serial.println(count);
  digitalWrite(drainPump, LOW);   
  if ((millis() - drain_Time) >= Draininterval){
      drain_Time = millis();
        flag++;  //stop this process
        digitalWrite(drainPump, HIGH);
        Serial.println("Drainning finnished");
        count = 0;
      } 
   }

void fillFun(int interval){
  count++;
  Serial.println(count);
  digitalWrite(fillValve, HIGH);;
  if ((millis() - fill_Time) >= interval){
    fill_Time = millis();
      flag++;
      digitalWrite(fillValve, LOW);
      Serial.println("Filling Completed");
  }
 }

Using the drainFun() function as an example I suspect you need to set drainTime = millis() when the function is first called but not during the repeat calls

I think the way to do that is to have a variable called drainRunning and normally it is false. Then the code in the drainFun() would be like this

void drainFun(int Draininterval){
  if (drainRunning == false) {
     drainStartTime = millis();
     drainRunning = true;
  }
  count++;
  Serial.println(count);
  digitalWrite(drainPump, LOW);   
  if ((millis() - drainStartTime) >= Draininterval){
            // line removed
        flag++;  //stop this process
        drainRunning = false;
        digitalWrite(drainPump, HIGH);
        Serial.println("Drainning finnished");
        count = 0;
      }
   }

There is no point setting drainStartTime = millis() when the draining is finished because you have no idea when it will start again.

You should be conscious that the changes I have suggested are what I would call "logic" changes - in other words, while they are implemented in code they really have little to do with code and all to do with the washing process. Even if you were instructing your kitchen maid to do the washing this sort of logic would be needed. I have seen several Threads on the Forum where people seemed not to see that distinction - not saying you don't, however.

...R

I have changed the interval from integer to long and this has solved the problem with the length of the run. I can now get it so far to a count of 6511. Still need to do some more testing to see if i can run this to a full fill of the washer.

Robin thanks again for the reply. You have lost me with this one at the moment. Is this change to do with the intervals interfering with each other. The following seems to work but have not tested what happens when i change the variables being sent to the function.

void loop() {
  switch(flag){
      case 0:
        drainFun (500);
      break;
      case 1:
        fillFun (40000);
      break;
  }  
}

void drainFun(long Draininterval){
  count++;
  Serial.println(count);
  digitalWrite(drainPump, LOW);   
  if ((millis() - drain_Time) >= Draininterval){
      drain_Time = millis();
        flag++;  //stop this process
        digitalWrite(drainPump, HIGH);
        Serial.println("Drainning finnished");
        count = 0;
      } 
   }

void fillFun(long interval){
  count++;
  Serial.println(count);
  digitalWrite(fillValve, HIGH);;
  if ((millis() - fill_Time) >= interval){
    fill_Time = millis();
      flag++;
      digitalWrite(fillValve, LOW);
      Serial.println("Filling Completed");
      Serial.println(interval);
  }

just run the fill on the dishwasher with an interval of 170000 and got a count of 23709 with the water level near the bottom door seal. So at this part is looking good. Still a lot to do.