Save millis() value and go to sleep

Greetings.
I'm using an ATMEGA328p uC to replace a washing machine control board. The project works fine so far, but I've encountered a problem when trying to implement a pause function.
I need to find a way to pause the cycle and resume it from where it left off. A section of the cycle, for example the mainwash, is composed like this:

-Fill (controlled by pressure switch). This is Stage 1
-Cold Wash (controlled by a 3 minute timer with millis()). Stage 2
-Heat (controlled by NTC). Stage 3
-Hot Wash (again, controlled by timer. 10 minutes this time around). Stage 4
-Drain(controlled by pressure switch). Stage 5

Let's say the machine has filled with water and it's 70 seconds in the cold wash phase. I'd like to use the pause button to digitalWrite all output pins LOW (this is no big deal) and wait until the button is pressed again (it should behave like a Pause/Resume button). There's a catch, though. When the cycle resumes, it needs to resume exactly from where it left off (the cold wash stage) and it needs to go on only for another 180-70=110 seconds before skipping to the next stage.

I read that sleep_mode() can be used in order to achieve similar results, but I can't wrap my head around what mode to use and whether this is even possible to do. I can't do something purely based on timing because there also also temperature and water level controlled stages which I can't foresee the length of.
I hope someone can help me find a solution to this problem. Any suggestion is very welcome. Thank you

In most sleep modes, millis() will not run.

Otherwise, simply store how many milliseconds have passed when the pause is pressed, and correct for that number when it's running again.

This sounds like a nice project using a RTC (Real Time Clock) that you do not have to set an absolute time is not important but time passing is. Set it up using Unix time and the math is all integer. The accuracy even on the cheapest should be way more then you need. A broken analog clock is correct twice a day in the US once a day in some other places. Side benefit many have battery backed RAM but you also have flash.
Good Luck & Have Fun!
Gil

You have completely confused and muddled yourself!

This machine is operating from mains power - there is no reason whatsoever to use a "sleep" mode; that is only ever for power saving.

When you pause it, you note the millis() value and you know the value when that cycle started, so you calculate how much time it has been operating. You also know how much time the cycle was to have taken.

When you restart, you set the new time of completion according to the desired cycle time minus the time already served. It's just maths.

And for a washing machine, you do not need a RTC! The accuracy of millis() will be just fine!

wvmarle:
In most sleep modes, millis() will not run.

This definitely is the simplest solution for the timing.

Mattia9914:
I can't do something purely based on timing because there also also temperature and water level controlled stages which I can't foresee the length of.

Mission impossible? XY problem?

I think that you should explain the possible reasons for pausing. Then pausing can be disallowed or truncated depending on the current state. Also the temperature profile could be continued without interruption, only machine motion (and water filling?) may be stoppable for some time.

wvmarle:
In most sleep modes, millis() will not run.

Otherwise, simply store how many milliseconds have passed when the pause is pressed, and correct for that number when it's running again.

I have been reading about sleep modes and it seems that Idle would be perfect for my scope since it allows to wake the uC up even from I/O and ADC pins. Can you confirm that it could be suitable for my idea? Currently the Pause/resume button is connected with other buttons, via a voltage divider, to pin A2

gilshultz:
This sounds like a nice project using a RTC (Real Time Clock) that you do not have to set an absolute time is not important but time passing is. Set it up using Unix time and the math is all integer. The accuracy even on the cheapest should be way more then you need. A broken analog clock is correct twice a day in the US once a day in some other places. Side benefit many have battery backed RAM but you also have flash.
Good Luck & Have Fun!
Gil

AFAIK there are production washing machines here in Europe which do use RTCs to show the cycle start and finish time. But perhaps it's a bit overkill

Paul__B:
You have completely confused and muddled yourself!

This machine is operating from mains power - there is no reason whatsoever to use a "sleep" mode; that is only ever for power saving.

When you pause it, you note the millis() value and you know the value when that cycle started, so you calculate how much time it has been operating. You also know how much time the cycle was to have taken.

When you restart, you set the new time of completion according to the desired cycle time minus the time already served. It's just maths.

And for a washing machine, you do not need a RTC! The accuracy of millis() will be just fine!

I had already thought about a solution like yours and I came to the conclusion that it was not possible. You may ask why. The reason is that heating and fill are NOT time based. The pressure switch calls for more water if the clothes are absorbing too much. More water means more time to heat it. And so on. A cycle run in two different conditions will never last equally as long beacuse of the tempearture of the water coming in, the difficulty with which the machine balances the load before the spin cycle, the temperature set (40,50,60°C). There's too many variables to do something that way
The reason I'd use sleep() is to, indeed, pause millis() from continuing to flow

DrDiettrich:
This definitely is the simplest solution for the timing.
Mission impossible? XY problem?

I think that you should explain the possible reasons for pausing. Then pausing can be disallowed or truncated depending on the current state. Also the temperature profile could be continued without interruption, only machine motion (and water filling?) may be stoppable for some time.

I could, for example, decide to pause the program in order to open the door and add or remove clothes, to avoid the machine to heat when I'm already using other energy consuming devices or to simply let clothes soak in warm soapy water.
If sleep is suitable for what I want to do then it'd be perfect to have it stop millis()

heating and fill are NOT time based.

You have two very different pause situations. The cold and hot washes which are time based have the simple millis() based solutions previously described.

For the process based states of heating and fill can you elaborate on how you want pause to function. What more is required than to turn off heaters and valves and then restart them?

cattledog:
You have two very different pause situations. The cold and hot washes which are time based have the simple millis() based solutions previously described.

For the process based states of heating and fill can you elaborate on how you want pause to function. What more is required than to turn off heaters and valves and then restart them?

When the machine is heating or filling and it enters pause mode I'd like it to switch off the heating element/water valve and write something to the display(eg. "CYCLE PAUSED; PRESS START TO RESUME"). It also writes the doorlock enable pin LOW, so that the door can be opened. Heating is not PID controlled so I just care about the machine re-powering the heating element after the pause happened if the water is not hot enough (the NTC is used to read the water temp). Same goes for filling: when the cycle is resumed, the machine keeps filling up if the pressure switch reads EMPTY.

Maybe a starting point.

// A basic retentive timer.  Runs when enabled.
// Holds value when enable false.  Reset when
// resetPin goes low.  Reset overrides enable

uint32_t accValue;
uint32_t preset = 3000; // 3 seconds
uint32_t previousMillis;
uint32_t currentMillis;

const byte enablePin = 8;
const byte resetPin = 10;
const byte doneLED = 13;

void setup() {
  Serial.begin(115200);
  pinMode(enablePin, INPUT_PULLUP);
  pinMode(resetPin, INPUT_PULLUP);
  pinMode(doneLED, OUTPUT);
}

void loop() {
  currentMillis = millis();
  if (digitalRead(enablePin) == LOW) { // pushbutton pressed, run timer
    accValue = accValue + currentMillis - previousMillis;
  }
  
  if (accValue >= preset) {
    accValue = preset;
    digitalWrite(doneLED, HIGH);
  }
  else digitalWrite(doneLED, LOW);
  previousMillis = currentMillis;
  
  if (digitalRead(resetPin) == LOW) accValue = 0;

  Serial.println(accValue); // if needed
}

OK, the problem is - that you have not properly defined the problem.

Unless you want to display the time of day, you do not need a RTC. The Arduino clock will be sufficiently accurate for cycles of less than 12 hours for this purpose. All the machines - dishwashers and such - I see do provide a "delay to start" which is defined not by time of day, but in hours from "now", so clearly do not have a RTC.

"Sleep" and "idle" modes are nonsense. There is no reason whatsoever to use a "sleep" mode; that is only ever for power saving which is irrelevant with the machine powered from the mains - as it clearly must be.

Timing is not a problem, you just determine the millis() at the start of a process, what it is when paused, and make decisions of what delays may or may not be required when you resume. It's just maths. (You are confusing having the program "idle waiting" with "sleeping". They have completely different purposes.)

You need to sit down and plan out what the requirement of all your proposed modes is. It seems you are only just beginning to do so. When you can adequately define the process of "pausing" and what must happen as a result, it will then be possible to work it out. :grinning:

The project works fine so far, but I've encountered a problem when trying to implement a pause function.

Please post the code you are trying to modify with a pause function.

Right, I'll try to better explain myself this time around.

The washing cycle is defined as in the attachment picture. At any point during the cycle, I want to be able to pause it.
What does pause do?

  1. It must stop the cycle from running (so it writes any output pins LOW and it freezes the timer from continuing to run and consequently skip the cycle to the next stage)
  2. It has to check two conditions: water temperature and water level. If the water temperature is above 40°C and the pressure switch reads HIGH on the High Water Level pin, it keeps the doorlock enabled; else it writes the doorlock pin LOW
  3. If the machine was spinning when the cycle was paused, it must resume from the beginning of the spin cycle when the system exits from the pause. The spin cycle is typically divided in 90RPM load distribution, 400 RPM spin, 800 RPM spin. If I pause the spin cycle when the machine is going at 400RPM, it must go back to the 90RPM stage.
  4. I have an I2C LCD connected to the Atmega. I'd like it to display "CYCLE PAUSED: PRESS START BUTTON TO RESUME".

So, the machine does the checks it has to do and then, when the cycle is paused, the machine WAITS for the USER to press the START button again.

When it resumes the cycle:

  1. It writes the doorlock pin HIGH
  2. It resumes from the stage it left off at the time it left

I hope I've explained myself. I'm really glad that you all are following the topic and I'd love to find a solution to this problem

In the picture attached, VAE means pressure switch controlled and the various Q mean the amount of water. T means temperature controlled via the NTC

WM_cycle_schematic.PNG
OK, so it seems you are getting clearer on the process.

There should be no problems. When the pause is called, you will note the time (millis()) at that point and determine how long the particular cycle has been running so far.

To resume, after verifying whatever preconditions apply, you can subtract the previous duration from the whole cycle time and continue for that balance of time unless this is the spin cycle in which case you repeat the necessary "spin-up" procedure and I would presume you now run for the balance of the full-speed time.

Since the low- and medium-speed parts of the cycle were timed separately in any case, you would only have been timing the actual full-speed phase anyway.

Paul__B:
WM_cycle_schematic.PNG
OK, so it seems you are getting clearer on the process.

There should be no problems. When the pause is called, you will note the time (millis()) at that point and determine how long the particular cycle has been running so far.

To resume, after verifying whatever preconditions apply, you can subtract the previous duration from the whole cycle time and continue for that balance of time unless this is the spin cycle in which case you repeat the necessary "spin-up" procedure and I would presume you now run for the balance of the full-speed time.

Since the low- and medium-speed parts of the cycle were timed separately in any case, you would only have been timing the actual full-speed phase anyway.

Alright. Let me see if I understood correctly:
What you call "particular cycle is, I assume, the different STAGES of the cycle, right? As in "duration of stage 5" of the cycle, right?

Paul__B:
WM_cycle_schematic.PNG

There should be no problems. When the pause is called, you will note the time (millis()) at that point and determine how long the particular cycle has been running so far.

You mean doing something like: millis()-programStartTime ?

I'm thinking: I create an array with the durations of the different stages of the program and, after resuming from the pause, I simply write arrayOfTime[currentStage]+=timeSpentInPause
Would something along the lines of this work?

Wait, maybe I got it (or maybe not :sweat_smile: )

if (stage == 1)
  {
    if (millis() - stageStartTime < stage1Length)
    {
      //do stuff
    }
    else
    {
      stage = 2;
      stageStartTime=millis();
    }
    else if (stage == 2)
  {
    if (millis() - stageStartTime < stage2Length)
    {
      //do stuff
    }
    else
    {
      stage = 3;
      stageStartTime=millis();
    }
  }

I'm guessing that what you mean is: when I enter pause, I grab the value of millis()- stageStartTime; then, in the pause function, I count how long the machine has been in pause; then, when the program is resumed, I add the time the time spent in pause and sum it to the stageStartTime, right?

Let's say I paused the machine when millis was 100000 and it stays paused for 30000 milliseconds. Let's consider that stageStartTime was 90000 and lenght was 200000.

Before the pause millis()-stageStartTime equals to 10000. There's still 190000 milliseconds to go.

When the pause is over, millis()-stageStartTime reads 130000 - 90000 = 40000. This would mean that if I resumed now there would only be 160000 milliseconds left before goint to the next stage. But, if I add 30000 to stageStartTime when exiting the pause function, I get 130000 - 120000 = 10000. This leaves another 190000 seconda to go

Correct me if I am wrong, nut this would seem to work, right?

Mattia9914:
I'm guessing that what you mean is: when I enter pause, I grab the value of millis()- stageStartTime; then, in the pause function, I count how long the machine has been in pause; then, when the program is resumed, I add the time the time spent in pause and sum it to the stageStartTime, right?

Err, no. You are over-complicating it! :grinning:

Ignore the time spent in pause.

When you enter pause, you do calculate the time already spent according to the current moment minus your original start time, but when you resume from the pause, you do not care what the original start time was, you take a new start time and step out the remaining "phase" time (the desired duration minus the time already spent) from that point. Much simpler. :sunglasses:

And note that you may pause more than once. On each occasion, you will simply accumulate the time performing that phase and on resuming, take that new value off the time to complete.

I'm sorry, I interpreted you solution in two different ways, one of which could cause a problem, but the other seems to work.

IDEA 1)

if (millis() - stageStartTime < stage1Length)
    {
      //do stuff
    }

I'm using numbers to make this clearer to me:

BEFORE PAUSING, let's say that millis()=100000, stageStartTime=90000, stage1Length=200000 (this doesn't vary)

timeAlreadySpent= millis() - stageStartTime=10000

NOW I pause the machine for 30 seconds (=30000 milliseconds). After 30 secs I press the resume button

AFTER 30 SECONDS it's time to exit the pause: now millis()=130000, stageStartTime=90000, stage1Length=200000. I now have to do the correction you suggested, so, just before exiting pause, I take a new stageStartTime which will now be equal to the current millis() (so 130000). The remaining phase time was 190000 millis (200000-10000). Now I simply write the following piece of code:

stageStartTime=millis()-(stage1Length-timeAlreadySpent)

BUT THIS EQUALS TO A NEGATIVE NUMBER: 130000 - (200000-10000)= -60000!!

Maybe, you suggest taking a new stageStartTime and then subtract the time alredy spent in the stage from the length? As in

stageStartTime=millis();
stage1Length-=timeAlreadySpent;

?

This way I'd have, immediately after the pause, millis()- stageStartTime=130000 and then stage1Length would be 200000-10000=190000. This would actually work! Is this what you meant?

Maybe, you suggest taking a new stageStartTime and then subtract the time alredy spent in the stage from the length?

This is correct. The key constructs are

timeElaspsed = currentMillis() - stateStartMillis;
timeRemaining= stateDuration - timeElapsed;

However, you never really will use timeElapsed, but You determine timeRemaining directly when the pause starts.

timeRemaining = stateStartMillis + stateDuration - currentMillis;

For timing a process state with a potential pause I would use code which looks like this. I would use Boolean control variables for pause, resume, and stateRunning which will help keep the timing correct and you will not need an additional state for pause.

Within a switch case for the state, you will have basic millis timing code like this

if(stateRunning && currentMillis - stateStartMillis >= stateDuration)
{
  stateRunning = false;
//do things when the state is finished
}

Now lets see what to do when you pause a state.

//some button code sets pause = true

if(pause && stateRunning)
{
//turn off components
//determine time remaining in the state when paused
stateRunning = false //stops code entry into state  timing block
timeRemaining = stateStartMillis + stateDuration - currentMillis;
}

And for resume

//some button code sets resume = true

if(resume)
{
pause = false;
resume = false;
stateRunning = true
stateStartMillis = millis();
stateDuration = timeRemaining;
//turn on the components for the state
}

Thank you very much cattledog and Paul__B. I already have boolean variables for the cycle running, so I just have to create the ones that I have not yet implemented. I'll try it out and let you know, but it seems like it will actually suit my goal perfectly. Thanks again