Using Millis() with RTC - Dim LEDs slowly at set time points

Hey Guys,

im trying to dimm two rows off high power LEDs via PWM at set timepoints (yes this is indeed another aquarium topic). To mark the start of certain events I would like to use a DS3231.

I got this to work great using Delays but when i try to replace this with Millis it is not working, can you point me in the right direction?

// Date and time functions using just software, based on millis() & timer
int ledPin1 = 6;
int ledPin2 = 5;
int brightness = 0;
int fadeAmount = 5;
int myhour = 0;
int myminute = 0;
int CO2Pin1 = 9;

#include <Arduino.h>
#include <Wire.h>         // this #include still required because the RTClib depends on it
#include "RTClib.h" // misschien aanpassen naar jouw lib naam!

unsigned long co2Millis;        //Waarde voor CO2 check
unsigned long fadeLEDMillis;    //Waarde voor LED fade
unsigned long currentMillis;    //Current value

const unsigned long intervalUp = 1000;
const unsigned long intervalDown = 1000;
const unsigned long CO2AAN = 1000;

RTC_Millis rtc;

void setup () {

pinMode(CO2Pin1, OUTPUT);

pinMode(ledPin1,OUTPUT);

pinMode(ledPin2,OUTPUT);

    Serial.begin(57600);
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.begin(DateTime(F(__DATE__), F(__TIME__)));

    DateTime now = rtc.now();
    
    myhour = now.hour();
    myminute = now.minute();

     Serial.print(myhour);
     Serial.print(":");
     Serial.println(myminute);

}

void loop () {
  

     
unsigned long currentMillis = millis();

if(myhour == 10 && myminute == 00) { //Infaden LED
  sunrise();
  }

if(myhour == 11 && myminute == 00) { //Licht steady aan
  steadysun();
  }

if(myhour == 19 && myminute == 00) { //LED uitfaden
  sunset();
  }

if(myhour == 9 && myminute == 00) { //CO2 aan
  co2aan();
  }

  delay(10000);
}


void sunrise()      // Functie voor starten LED
{
  for(brightness = 0; brightness < 175; brightness = brightness + fadeAmount) {

    if (currentMillis - fadeLEDMillis >= intervalUp){
      analogWrite(ledPin1,brightness);
          Serial.print("OMHOOG");
          Serial.print(brightness);
       
      analogWrite(ledPin2,brightness);
          Serial.print("OMHOOG");
          Serial.print(brightness);
          
    fadeLEDMillis = currentMillis; //IMPORTANT to save start time of current LED

    }
  }
}

void steadysun()  //Functie voor aanhouden LED
{  
    analogWrite(ledPin1,175);
    analogWrite(ledPin2,100);
}

void sunset()     // Functie voor uitschakelen LED
{
  for(brightness = 175; brightness >= 0; brightness = brightness - fadeAmount) {

    if (currentMillis - fadeLEDMillis >= intervalDown){
      analogWrite(ledPin1,brightness);
          Serial.print("NAAR BENEE");
          Serial.print(brightness);

      analogWrite(ledPin2,brightness);
          Serial.print("NAAR BENEE");
          Serial.print(brightness);

    fadeLEDMillis = currentMillis; //IMPORTANT to save start time of current LED according to https://forum.arduino.cc/index.php?topic=503368.0
  }
 }
}

void co2aan()
{
  analogWrite(CO2Pin1,HIGH);
  if (currentMillis - co2Millis >= CO2AAN){
  analogWrite(CO2Pin1,LOW); 

  co2Millis = currentMillis; //IMPORTANT to save start time of current LED
    
  }
}

You need to use a "state" which is set by the current time, something like this:

byte state = 0; //Global declaration

void loop() {
  if (time == 10:00) state = 1; //sunrise
  else if (time == 11:00) state = 2; //steadysun
  //and so on

  //Handle current state
  switch(state) {
    case(1): sunrise(); break;
    case(2): steadysun(); break;
    //And so on
  }
}

You can then use millis in "sunrise" and "steadysun" and when they are done with fading, they reset "state" to 0.

but when i try to replace this with Millis it is not working,

It IS working.

It may not be doing what you want, but that doesn't mean that it is not working.

If you really want help, you'll tell us what the code ACTUALLY does, and how that differs from what you want.

@Danois90: Thanx, I will look into state

@PaulS; You are right, it does not give an error when I compile. But when I run the script it should slowly fade my LEDs on. Right now it doesn't, also in my Serial i don't read the line it should give when the fade is in progress

also in my Serial i don't read the line it should give when the fade is in progress

I'm sure that this means more to you than it means to me.

I suspect that you mean that "in the Serial Monitor, I don't see the line...".

But, lets examine what happens. The Arduino starts up, and, eventually, setup() is called. You read the time on the RTC, and store the hour and minute values in myrour (dumb name) and myminute (equally dumb).

The setup() function ends, and loop() is called. Lets say that you booted up the Arduino at 9:57. myhour is not 10, so the first three if statements evaluate to false. It is 9, so the first part of the 4th statement is true, but myminute isn't 0, so the whole statement is false. There is, therefore, nothing for loop() to do on this iteration, so loop() ends.

The loop() function is called again, but myhour and myminute haven't changed, so loop() again has nothing to do, so it ends.

And is called a few billion more times, but nothing has changed, so loop() never has anything to do except wear out.

At a minimum, you MUST update myhour and myminute, on EVERY pass through loop(), so that, sometimes, there will be something to do.

Next, we'll assume that you've done that, and myhour eventually becomes 10 and myminute eventually becomes 0.

Hey, look, its time to call sunrise().

The global currentMillis is 0. That is NOT the variable that loop() updated. fadeMillis is 0, too.

0 - 0 is 0, which is not greater than 1000, so the if statement is false, and the for loop iterates again, over and over, never doing anything, until brightness has incremented from 0 to 175, in steps of 5.

Then, sunrise() ends, and loop() is called again.

It's still 10:00, because that "fading" didn't take long, so sunrise() gets called again, and produces the same non-results.

Eventually, it gets to be 10:01, and sunrise doesn't get called again.

Eventually, it gets to be 11:00, and steadysun() gets called (with the LEDs off).

It turns the lights on bang, and gets called over and over, until 19:00, even though all it is doing is turning the lights on that are already on.

See the problem?

As Danois90 says, you need a state machine and NO for loops. Not a single one. On each pass through loop(), you are in some state - the sun is rising, the sun is high, the sun is setting, or shut-the-f**king-lights-off-we're-trying-to-sleep-here.

It might, or might not, be time to change state - Oh, my god, look at the time.

In any given state, like "the sun is rising", it might be time to change the brightness, or it might not. That depends on when you last changed the brightness. The change might be positive (the sun is rising) or negative (the sun is setting). If it IS time to make a change, make the change, apply the change to the LEDs, AND record when the change is made.

Thanx for the clear explanation, I will from now on be a lot more specific in my question. Point very well taken :smiley:

I do have one question do about the state function, because right now it feels a bit like the delay function.

Can I put more then one function behind the state? Or am I completely misunderstanding state?

Example; Lets say I want to switch on a Relay at 9;30 and the LEDS fade on at 10:00 would code then look like this?

byte state = 0; //Global declaration

void loop() {
  if (time == 9:30) state = 1; //Relay
  else if (time == 10:00) state = 2; //Sunrise and Relay
  //and so on

  //Handle current state
  switch(state) {
    case(1): relay(); break;
    case(2): relay,sunrise(); break;
    //And so on
  }
}

You can execute multiple statements for each case label:

switch(state) {
case (0):
  statement1();
  statement2();
  break; //exit case
case (1):
  statement3();
  statement4();
  break; //exit case
}

But I assume that you only want to invoke relay once and your case statement will be repeated as long as the state remains the value of that particular case statement. So you should invoke your relay according to the time and skip the state for that.

if (time == 9:30) Relay();
else if (time == 10:00) {
  Relay();
  state = 1;
}
//and so on

Thank you, I had a little mindfart there thinking I would have to digitalwrite(HIGH); to the relay pin every loop. But off course it will stay high until I write a LOW to a

You could also just check the state of the pin used for the relay:

void Relay(bool enable) {
  byte state = enable ? HIGH : LOW;
  if (digitalRead(relayPin) != state) digitalWrite(relayPin, state);
}

But in the end, it does not matter that much. I was just assuming that your Relay() method was toggeling the state of the relay which would have caused the relay to constantly switching which would be unfortunate.

I indeed just want to toggle the Relay only twice a day. ON in the morning and OFF in the afternoon.

Since i am asking away anyway, could you give a short explanation to your Relay code.

What i get so far:

-You create a function called Relay; but what does bool enable do?

-Then you enable byte state? This i don't get

  • Then you check the state of the pin by reading it and compare it to the state, only to write the state anyway right?

"enable" is a boolean variable passed to the function:

//do something
Relay(true); //Turn relay on
//do something else
Relay(false); //Turn relay off

The relay function could in fact be even simpler, since boolean is either 0 (false) or 1 (true):

void Relay(bool enable) {
  if (digitalRead(relayPin) != (byte)enable) digitalWrite(relayPin, (byte)enable);
}
void Relay(bool enable) {
  if (digitalRead(relayPin) != (byte)enable)

A bool is an 8 bit unsigned type. A byte is an 8 bit unsigned type. No cast is needed. Even if one were, well, that's the default cast so the explicit cast provides no benefit.

Ok, i think i'm starting to get the point. But PaulS in your code you don't write to the pin, so as far as i understand your code does not change the state if the read comes uit FALSE right?

mDutch:
Ok, i think i'm starting to get the point. But PaulS in your code you don't write to the pin, so as far as i understand your code does not change the state if the read comes uit FALSE right?

I only included a snippet of Danois90's code, to show there the cast was unnecessary, and to explain why. You would need to use all of Danois90's code, minus the unnecessary cast.

Got it, thanx for the patience and getting me up to speed a bit more!

Ok, after updating the script to a simpler form the Fading is still not happening with the delay. In the serial monitor i can see the fade happens, but it just happens really fast...

Could you help me with what is wrong with my Millis part?

#include <Arduino.h>
#include <Wire.h>         // this #include still required because the RTClib depends on it
#include "RTClib.h" 

// Date and time functions using just software, based on millis() & timer

const byte ledPin1 = 6;
const byte ledPin2 = 5;

byte brightness = 0;
byte fadeAmount = 5;
byte state = 0;

int myhour = 0;
int myminute = 0;

unsigned long fadeMillis;    //Waarde voor LED fade
unsigned long currentMillis;

const unsigned long fadeInterval = 10000;

RTC_Millis rtc;

void setup () {

pinMode(ledPin1,OUTPUT);

pinMode(ledPin2,OUTPUT);

    Serial.begin(57600);
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.begin(DateTime(F(__DATE__), F(__TIME__)));

    DateTime now = rtc.now();
    
    myhour = now.hour();
    myminute = now.minute();

     Serial.print(myhour);
     Serial.print(":");
     Serial.println(myminute);

}

void loop () {

currentMillis = millis();
  
    DateTime now = rtc.now();
    myhour = now.hour();
    myminute = now.minute();

if(myhour == 19 && myminute == 20) state = 1;
else if(myhour == 11 && myminute == 00) state = 2;
else if(myhour == 18 && myminute == 00) state = 3;


switch(state) {
  case(1): sunrise(); break;
}
  }


void sunrise()      // Functie voor starten LED
{
  
  
    if (currentMillis - fadeMillis >= fadeInterval){
      
      for (brightness = 0; brightness < 140; brightness = brightness + fadeAmount) {
      analogWrite(ledPin1,brightness);
          Serial.print("UP");
          Serial.print(brightness);
       
      analogWrite(ledPin2,brightness);
          Serial.print("UP");
          Serial.print(brightness);
          
    fadeMillis = currentMillis; //IMPORTANT to save start time of current LED

    }
  }
}

The "loop()" is being repeated very fast, so you either need to put in some delay to slow it down (please don't, though) or you should consider the fading LED as being attached to a time line which progresses a 1000 times per second. In order to calculate the current progress, you then need something like:

percentDone = (currentTime - startedTime) / transitionDuration;
if (percentDone < 0) percentDone = 0; //Transition not started yet
else if (percentDone > 1) percentDone = 1; //Transition complete
transitionAt = ((transitionMax - transitionMin) * percentDone) + transitionMin;

This requires no delay and is the basis for any time line calculation. This must be done in floating points, since percentDone is a value between 0 and 1. transitionAt will then be the value set to the LED.

Did I not mention that you MUST quit using for loops in the functions that loop() calls? State machines do NOT use for loops. The ONLY looping that happens is because loop() is called over and over.

When sunrise() is called, it may, or may not, be time to change the intensity of the LEDs. But, it will only be time, or not, to make ONE change.