Confused on how to get Millis to pause in between LED fading

Hi all -- I've been studying Arduino for the better part of a year, and am becoming more comfortable using things like the Millis function. I've come across a post where the sketch writer used Millis to fade an LED up and down (see below). But I'm trying to fade the LED up, stop for 5 or so seconds, and then fade down. I've searched the topic all day but since everyone's code is different, its a bit harder to follow exactly what they're doing.

Im thinking along the lines of -- after the led fades up, you add something like

if (currentMillis >= previousFadeMillis+5000) {
// if currentMillis is greater than what it was before, by 5000 milliseconds
fadeValue = maxPWM - fadeIncrement
}

I think my train of logic is on the right path, but I guess I'm not sure exactly how to code it. If anyone can give me a boost I'd appreciate it!! Thanks!

const byte pwmLED = 8;
 
 
// define directions for LED fade
#define UP 0
#define DOWN 1
 
// constants for min and max PWM
const int minPWM = 0;
const int maxPWM = 255;
 
// State Variable for Fade Direction
byte fadeDirection = UP;
 
// Global Fade Value
// but be bigger than byte and signed, for rollover
int fadeValue = 0;
 
// How smooth to fade?
byte fadeIncrement = 5;
 
// millis() timing Variable, just for fading
unsigned long previousFadeMillis;
 
// How fast to increment?
int fadeInterval = 50;
 
void setup() {
  // put pwmLED into known state (off)
  analogWrite(pwmLED, fadeValue); 
}
 
void doTheFade(unsigned long currentMillis) {
  // is it time to update yet?
  // if not, nothing happens
  if (currentMillis - previousFadeMillis >= fadeInterval) {
    // yup, it's time!
    if (fadeDirection == UP) {
      fadeValue = fadeValue + fadeIncrement;  

     
      if (fadeValue >= maxPWM) {
        // At max, limit and change direction
        fadeValue = maxPWM;
        fadeDirection = DOWN;
      }


    }  else {
      //if we aren't going up, we're going down
      fadeValue = fadeValue - fadeIncrement;
      if (fadeValue <= minPWM) {
        // At min, limit and change direction
        fadeValue = minPWM;
        fadeDirection = UP;
      }
    }
    // Only need to update when it changes
    analogWrite(pwmLED, fadeValue);  
 
    // reset millis for the next iteration (fade timer only)
    previousFadeMillis = currentMillis;
  }
}
 
void loop() {
  // get the current time, for this time around loop
  // all millis() timer checks will use this time stamp
  unsigned long currentMillis = millis();
    
  doTheFade(currentMillis);
 
}

Read and follow the links in this URL.

https://forum.arduino.cc/index.php?topic=503368.0

I actually went through that earlier, but I'll take another stab at it and try to create an original code. The only reason I tried to add onto the existing code is because it does everything I need except the hold. I'll report back!

Time to stop copying other peoples code and start writing your own.
No matter how hard you look, you will not find all the features you need.
Looking at other code should be for learning techniques.

if (currentMillis >= previousFadeMillis+5000) {
The above line does not represent the correct way to use BWD style coding.

The title of this Thread suggests a false understanding of millis()

how to get Millis to pause in between LED fading

millis() is never paused, just as you never stop the clock on your kitchen wall.

If you want an interval between things then save the value of miils() at the start of the interval and use the usual

if (millis() - intervalStartMillis >=interval) {

to determine the end of the interval.

Think about how you would use your kitchen clock to wait 20 minutes between the end of cleaning the oven and the start of doing the ironing.

...R

Your program has 3 states

0 - fading up
1 - waiting
2 - fading down

Declare a variable to hold the current state number. Use millis() to time the actions for the current state. When the actions for the current state are finished change the state variable and start timing the next one.

You need to (mentally) convert a span of time into a percentage of progress when working with millis(). If something is started when the arduino is powered on and has to run for 5 seconds, the code would be something like:

#define DURATION 5000.0f
#define PWM_PIN 8

unsigned long startMillis, currentMillis;

void setup()
{
  //Other setup here
  startMillis = millis();
  pinMode(PWM_PIN, OUTPUT);
  analogWrite(PWM_PIN, 0);
}

void loop()
{
  currentMillis = millis();
  float percentDone = (float)(currentMillis - startMillis) / DURATION;
  if (percentDone < 0)
  {
    //This block is entered if the operation has not started yet
    analogWrite(PWM_PIN, 0);
  }
  else if (percentDone > 1)
  {
    //This block is entered if the operation has completed
    analogWrite(PWM_PIN, 255);
  }
  else
  {
    //This block is entered when the operation is partially done
    //percentDone will be in the range 0.0 to 1.0 where 0
    //is the starting point and 1.0 is the ending point. You can use the
    //value to easaly map another, eg 0..255 for analogWrite, like this:
    analogWrite(PWM_PIN, round(255.0f * percentDone));
  }
}

Please note that floating point operations are heavy on the Arduino and if they are avoidable they should be. You could also "cheat" and use a library to accomplish your task with minimal code / effort:

#include <TDuino.h>
#define PWM_PIN 8

void timelineHandler(byte slot, float progress)
{
  if (slot == 0) analogWrite(PWM_PIN, TL_MapToInt(progress, 0, 255);
  else analogWrite(PWM_PIN, TL_MapToInt(progress, 255, 0);
  /*if (tl.firstActive() < 0)
  {
    //This block will be entered when the timeline has completed
  }*/
}

TTimeline tl(timelineHandler, 2);

void setup()
{
  //Other setup here
  pinMode(PWM_PIN, OUTPUT);
  tl.set(0, 1000, 0); //Slot 0, duration 1 sec, start now
  tl.set(1, 1000, 6000); //Slot 1, duration 1 sec, start after 6 secs (1 sec fade on + 5 sec pause)
}

void loop()
{
  tl.loop();
}

TDuino docs for TTimeline. None of the code above has been tested..

http://www.thebox.myzen.co.uk/Tutorial/State_Machine.html

Danois90:
You need to (mentally) convert a span of time into a percentage of progress when working with millis().

I can't imagine why.

And millis() works with unsigned long values. Why introduce the complexity, the inaccuracy and the sloth of floating point values?

...R

So I sat down for a few hours and came up with this --

I also added in a PIR sensor to trigger the led turning on. The only issue I'm having now is that because the PIR has the 5sec "delay" built into it, my input (and LED) stays high for a minimum of 5seconds. I was thinking that for

if (ledStatus == HIGH) {                                  // IF the LED status is HIGH
      if (currentMillis - startMillis > ledOnTime) {          // AND if a period longer than the "set LED ON" time has expired
        digitalWrite (ledPin, LOW);                           // Turn the LED off

Could I write something like   if (currentMillis - startMillis > ledOnTime - 5000)

where the -5000 would act as a way to work around the PIR delay keeping everything on? But I also feel like if I don't use the ledStatus as my "off trigger" then I may be able to work around it as well.

Any suggestions? Thanks for the motivation!

const int PirInputPin = 2;                // PIR is connected to pin 2
const byte ledPin = 8;                    // LED is on Pin 8

int PirVal = 0;                           // Initial variable to store sensor status
int PirState = LOW;                       // Initial sensor condition
int ledStatus = LOW;                      // LED on or off

unsigned long startMillis;                // Starting time for current loop
unsigned long currentMillis;              // Current time for current loop
const unsigned long ledOnTime = 1;        // How long you want the LED to stay on (+5000ms)

void setup() {

  pinMode(ledPin, OUTPUT);               // The LED is an output
  pinMode(PirInputPin, INPUT);           // The PIR is an input

  startMillis = millis();

}

void loop() {
  currentMillis = millis();                        // Time variable
  PirVal = digitalRead(PirInputPin);               // Read the input value of the PIR


  if (PirVal == HIGH) {                     // check if the PIR has detected motion
    digitalWrite(ledPin, HIGH);             // If it has, turn on the LED
    ledStatus = HIGH;                       // Change the LED status to HIGH
    startMillis = currentMillis;            // Set the start time for the next loop to this current time

    //if (PirState == LOW) {                // AND if the PIR state is currently LOW, set it to currently HIGH
    //  PirState = HIGH;

    if (ledStatus == HIGH) {                                  // IF the LED status is HIGH
      if (currentMillis - startMillis > ledOnTime) {          // AND if a period longer than the "set LED ON" time has expired
        digitalWrite (ledPin, LOW);                           // Turn the LED off
        ledStatus = LOW;                                      // Change the LED status to LOW
        startMillis = currentMillis;                          // Set the start time for the next loop to this current time

      }

    }
  }
  else if (ledStatus == HIGH) {                         // If the above isnt happening AND the LED status is set to HIGH
    if (currentMillis - startMillis > ledOnTime) {      // And if a period longer than the "set LED ON" time has expired
      digitalWrite (ledPin, LOW);                       // Turn the LED off
      ledStatus = LOW;                                  // Change the LED status to LOW
      startMillis = currentMillis;                      // Set the start time for the next loop to this current time

      //    if (PirState == HIGH) {                     // If the PIR state is currently HIGH, set it to currently LOW
      //     PirState = LOW;
    }

  }

}

//  }

//}

Negative time makes no sense. How can you know that the input is going to start 5 seconds in the future?

Instead of turning the LED on and starting the timer when the PIR is HIGH, detect the change in the PIR. That means you should record what it was on the previous loop and compare against that.

It makes sense as far as the negative time if it did the calculation during the loop and read if the time passed is greater than ~300ms~ (for example) but I also understand what you mean. is there a way to tell it directly that if its greater than 5000ms or 9000ms what to do? Like if I made time a variable? something like:

ledOnTime = 5000

if (ledStatus == HIGH) {                                  // IF the LED status is HIGH
      if (currentMillis - startMillis > ledOnTime) {          // AND if a period longer than the "set LED ON" time has expired
        digitalWrite (ledPin, LOW);                           // Turn the LED off

I will also try the idea of switching during the change. I think I was kind of onto that track when I had the PirState in my loop, but I commented those lines out.

MorganS:
http://www.thebox.myzen.co.uk/Tutorial/State_Machine.html

I like! Im going to try this as well --

That will turn it off ledOnTime milliseconds after it went on. If the PIR is still detecting movement OR still within its 5-second period, it will turn on again a millisecond later. Is this what you want?

Point taken! Ill rewrite it tomorrow as a state machine and see where we get :wink: thanks for the help so far!

Well, I'm closer. I got the led to come on via the PIR and I got it to only stay on for as long as I set my ledOnTime for -- but the only issue I have now is that its not recording the previousPIR state as the current state, because once the loop restarts, it read the new PIR state as the current...and since the PIR stays latched for 5seconds, it will start the whole process again.... basically making my LED come on twice in one motion sense (atleast I think thats what the issue is).

Is there a way to work around this?

const int PirInputPin = 2;                // PIR is connected to pin 2
const byte ledPin = 8;                    // LED is on Pin 8

int PirVal = 0;                           // Initial variable to store sensor status
int PreviousPirVal = 0;
int PirState = LOW;                       // Initial sensor condition
boolean ledStatus = 0;                      // LED on or off
boolean PreviousLedStatus = 0;
int ledState = 0;

unsigned long startMillis;                // Starting time for current loop
unsigned long currentMillis;              // Current time for current loop
const unsigned long ledOnTime = 4000;        // How long you want the LED to stay on

void setup() {

  pinMode(ledPin, OUTPUT);               // The LED is an output
  pinMode(PirInputPin, INPUT);           // The PIR is an input

  startMillis = millis();

}

void loop() {
  currentMillis = millis();                        // Time variable
  PirVal = digitalRead(PirInputPin);               // Read the input value of the PIR


  if (PirVal != PreviousPirVal) {           // Check if the current PIR value is different from its last value
    digitalWrite(ledPin, HIGH);             // If it has changed, turn on the LED
    ledStatus = 1;                          // Change the LED status to 1
    startMillis = currentMillis;            // Set the start time for the next loop to this current time
    PreviousPirVal = PirVal;                // Set the previous Pir Value equal to the current value, for the next loop


    if (ledStatus == 1) {                                     // IF the current LED status is 1
      if (currentMillis - startMillis >= ledOnTime) {          // AND if a period longer than the "set LED ON" time has expired
        digitalWrite (ledPin, LOW);                           // Turn the LED off
        ledStatus = 0;                                        // Change the LED status to 0
        startMillis = currentMillis;                          // Set the start time for the next loop to this current time
        PreviousLedStatus = ledStatus;                        // Set the previous LED status equal to the current LED status
      }

    }
  }
  else if (PirVal == PreviousPirVal) {                  // If the current Pir value is the same as the previous PIR value
    if (currentMillis - startMillis >= ledOnTime) {      // And if a period longer than the "set LED ON" time has expired
      digitalWrite (ledPin, LOW);                       // Turn the LED off
      ledStatus = 0;                                    // Change the LED status to LOW
      startMillis = currentMillis;                      // Set the start time for the next loop to this current time
      PreviousPirVal = PirVal;
    }

  }

}

Try
if (ledStatus == 0 && PirVal != PreviousPirVal) {

larryd:
Try
if (ledStatus == 0 && PirVal != PreviousPirVal) {

larryd:
Try
if (ledStatus == 0 && PirVal != PreviousPirVal) {

I tried this, but it still didn't work. But it had me thinking -- could I run a command that had another variable, something like "timeTriggered" and make that equal to 5 seconds? Therefore if the timeTriggered <= last recorded time, the code won't execute?

Something like

if (PirVal != PreviousPirVal && timeTriggered >= currentMillis)

I hope that makes sense. Thanks!

As suggested in post #5

enum states
{
  FADING_UP,
  WAITING,
  FADING_DOWN,
  DO_NOTHING
};
byte currentState = FADING_UP;
const byte ledPin = 3;
unsigned long startTime;
unsigned long currentTime;
unsigned long fadePeriod = 20;
unsigned long waitPeriod = 5000;
unsigned int ledLevel = 0;

void setup()
{
  Serial.begin(115200);
  pinMode (ledPin, OUTPUT);
}

void loop()
{
  currentTime = millis();
  switch (currentState)
  {
    case FADING_UP:
      if (currentTime - startTime >= fadePeriod)//time to change LED level
      {
        ledLevel++;
        analogWrite(ledPin, ledLevel);
        if (ledLevel == 255)
        {
          currentState = WAITING;
          startTime = currentTime;
        }
        startTime = currentTime;
      }
      break;

    case WAITING:
      if (currentTime - startTime >= waitPeriod)
      {
        currentState = FADING_DOWN;
        startTime = currentTime;
      }
      break;

    case FADING_DOWN:
      if (currentTime - startTime >= fadePeriod)//time to change LED level
      {
        ledLevel--;
        analogWrite(ledPin, ledLevel);
        if (ledLevel == 0)
        {
          currentState = DO_NOTHING;
        }
        startTime = currentTime;
      }
      break;
  }
}

UKHeliBob:
As suggested in post #5

enum states

{
  FADING_UP,
  WAITING,
  FADING_DOWN,
  DO_NOTHING
};
byte currentState = FADING_UP;
const byte ledPin = 3;
unsigned long startTime;
unsigned long currentTime;
unsigned long fadePeriod = 20;
unsigned long waitPeriod = 5000;
unsigned int ledLevel = 0;

void setup()
{
  Serial.begin(115200);
  pinMode (ledPin, OUTPUT);
}

void loop()
{
  currentTime = millis();
  switch (currentState)
  {
    case FADING_UP:
      if (currentTime - startTime >= fadePeriod)//time to change LED level
      {
        ledLevel++;
        analogWrite(ledPin, ledLevel);
        if (ledLevel == 255)
        {
          currentState = WAITING;
          startTime = currentTime;
        }
        startTime = currentTime;
      }
      break;

case WAITING:
      if (currentTime - startTime >= waitPeriod)
      {
        currentState = FADING_DOWN;
        startTime = currentTime;
      }
      break;

case FADING_DOWN:
      if (currentTime - startTime >= fadePeriod)//time to change LED level
      {
        ledLevel--;
        analogWrite(ledPin, ledLevel);
        if (ledLevel == 0)
        {
          currentState = DO_NOTHING;
        }
        startTime = currentTime;
      }
      break;
  }
}

This makes total sense -- I understood the post 5 statement, but I think I'm too much of a rookie to conceptualize it from scratch haha.

But with this, since I'm using the PIR to start things up, is it still going to give me an issue with the 5sec delay? I won't be home for another 3hrs to try it out. I've been at work all day and have spent a good 4hours thinking about how to fix the sketch !!

As it stands the fade up, wait, fade down, do nothing sequence starts when the Arduino is powered up or reset but it should show you how to use a state machine.

To control it with a PIR you could introduce a state, perhaps AWAITING_TRIGGER, and only come out of that state when the PIR is triggered. If you want to exit one of the states because of PIR triggering then add that test within the code for the state and change to a different state when the PIR is triggered, or stops being triggered.

Once you have grasped the principle of using a state machine you will find it very flexible.