Adding code to PWM LED dimming

Hello all, I came across this nifty code for PWM dimming a sunrise and sunset but it is pretty basic and Id like to add some simulated cloud cover throughout the day as well as the ability to change the sunrise and sunset times with some buttons and possibly a test button that will fade all the channels from their current state down to 0 then up to 100% and back to their previous state in like 30-60 seconds.
here is the main code I'm starting with:

const int kChan0Pin = 9; // Channel 0 Pin
const int kChan1Pin = 10; // Channel 1 Pin

// All times are in seconds since midnight (valid 0 - 86399)
const long kTurnOn = 32400; // time dawn begins - 0900hrs
const long kTurnOff = 75600; // time sunset begins - 2100hrs

/*
 * Light "state" represents the PWM duty cycle for each channel This normally
 * dictates light intensity. It is an array { duty_chan_1, duty_chan_2 }.
 * Possible values for duty cycle are 0 - 1023.
 */
const int kDayState[] = { 600, 400 }; // daytime LED state
const int kNightState[] = { 0, 0 }; // nighttime LED state

/*
 * Duration (in seconds) of fade.  At the moment the only fades are sunrise and
 * sunset but this value will apply to any other fades you came up with
 */
const long kFadeDuration = 3600; // 60 minutes

long ctr;

/* hold state info */
int state_chan1, state_chan2;

/*
 * fader -- Determine output state for a given time to provide smooth fade from
 * one state to another.
 *     Args:
 *     start_time  -- time (in seconds) of start of fade
 *     start_state -- beginning state
 *     end_state   -- ending state
 *     out         -- array to update with state
 */
void fader(long start_time, const int start_state[], const int end_state[], int out[2]) {

  float per_second_delta_0 = (float) (end_state[0]-start_state[0])/kFadeDuration;
  float per_second_delta_1 = (float) (end_state[1]-start_state[1])/kFadeDuration;

  long elapsed = ctr-start_time;

  out[0] = start_state[0] + per_second_delta_0 * elapsed;
  out[1] = start_state[1] + per_second_delta_1 * elapsed;
}

// return seconds elapsed since midnight
long seconds_since_midnight() {
  time_t t = now();
  long hr = hour(t);
  long min = minute(t);
  long sec = second(t);
  long total = hr * 3600 + min * 60 + sec;
  return total;
}

// set output state
void set_state(const int state[]) {
  if (state[0] >= 0 && state[0] <= 1023) {
    Timer1.setPwmDuty(kChan0Pin, state[0]);
    state_chan1 = state[0]; }
  if (state[1] >= 0 && state[1] <= 1023) {
    Timer1.setPwmDuty(kChan1Pin, state[1]);
    state_chan2 = state[1]; }
}

/*
 * determine_state -- This is where the actual timing logic resides.  We
 * examine ctr (seconds since midnight) and then set output state accordingly.
 * Variable ctr rolls back to 0 at midnight so stages that cross midnight (ie:
 * nighttime) are broken up into two stages.
 */
void determine_state() {
  if ( ctr >= 0 && ctr < kTurnOn ) { // night
      set_state(kNightState);
  } else if ( ctr >= kTurnOn && ctr <= (kTurnOn+kFadeDuration) ) { // sunrise
    int foo[2];
    fader(kTurnOn, kNightState, kDayState, foo);
    set_state(foo);
  } else if ( ctr > (kTurnOn+kFadeDuration) && ctr < kTurnOff ) { // day
    set_state(kDayState);
  } else if ( ctr >= kTurnOff && ctr <= (kTurnOff+kFadeDuration) ) { // sunset
    int foo[2];
    fader(kTurnOff, kDayState, kNightState, foo);
    set_state(foo);
  } else if ( ctr > (kTurnOff+kFadeDuration) && ctr < 86400 ) { // night
    set_state(kNightState);
  }
}

First, coding in the ability to set the sun rise/set times. As far as the buttons go I have some code from an RTC timer sketch:

//You need 4 buttons like this,to enter the set points, with 10K resistor to ground (for each one)

const int DOWN_BUTTONon =3;        //Buttons Pins
const int UP_BUTTONon =4;
const int DOWN_BUTTONoff =5;       
const int UP_BUTTONoff =6;

//-----Variables for debouncing------//

boolean lastDownONButton = LOW;
boolean currentDownONButton = LOW;
boolean lastUpONButton = LOW;
boolean currentUpONButton = LOW;
boolean lastDownOFFButton = LOW;
boolean currentDownOFFButton = LOW;
boolean lastUpOFFButton = LOW;
boolean currentUpOFFButton = LOW;


int set_on = 12;        //The default "ON" desired time
int set_off= 12;        //The default "OFF" desired time

How do I work this into the sketch? Can I tell it that int set_on is const long kTurnOn and int set_off is const long kTurnOff?

Secondly, the matter of stimulating clouds. Can I just add cloud states to day and night like this?

const int kDayState[] = { 900, 900 }; // daytime LED state 88% light
const int kNightState[] = { 0, 0 }; // nighttime LED state 0 % light
const int kCloudState1[] = {780,780}; // light clouds 76% light
const int kCloudState2[] = {675,675}; // medium clouds 66% light
const int kCloudState3[] = {580,580}; // dark clouds 56% light

Then, how do I define the logic to choose which state and when? I was thinking something along the lines of the random() function; How can I tell it between sunrise and sunset to pick 4 random times to fade to a random cloud state?

Lastly, how can I tell it to interrupt the current state at the push of a button to sweep from current state down to {0,0} then up to {1000,1000} and back to its current state? can I add a const int kTestState[]; ?

I know I'm a beginner at this and asking alot of questions, so I appreciate anyone with the patience to help, i'm having a hard time finding tutorials with my exact needs

It would help if you posted the complete original program and not just a section of it.

my apologies here it is:

/*
 * Name:	tank_control.pde
 * Author:	User "sink" at plantedtank.net forums
 * URL:		http://bitbucket.org/akl/tank-control
 *
 * This is control code for an aquarium lighting system.  It is intended to be
 * run on an Arduino microcontroller board.  It allows independant
 * high-resolution control of two PWM outputs (normally connected to LED
 * drivers) and complete flexibility with respect to intensity, timing
 * schedules, and sunrise/sunset.
 *
 * This code requires the following libraries: Wire, TimerOne, Time, DS3231RTC.
 * A bundle of the required libraries (except for Wire, which you should
 * already have) is located in the downloads section of the URL above.  You can
 * always find the latest copy of the code at that location.
 */

/*
 * Copyright (c) 2011, User "sink" at plantedtank.net forums
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.  
 */

#include <TimerOne.h>
#include <Time.h>
#include <Wire.h>
//#include <DS1307RTC.h>
#include <DS3231RTC.h>

/*
 * IMPORTANT:  These *must* be the pins corresponding to the Timer1 timer on
 * the ATmega168/328.  These are digital pins 9 and 10 on the Uno/Duemilanove.
 */
const int kChan0Pin = 9; // Channel 0 Pin
const int kChan1Pin = 10; // Channel 1 Pin

// All times are in seconds since midnight (valid 0 - 86399)
const long kTurnOn = 32400; // time dawn begins - 0900hrs
const long kTurnOff = 75600; // time sunset begins - 2100hrs

/*
 * Light "state" represents the PWM duty cycle for each channel This normally
 * dictates light intensity. It is an array { duty_chan_1, duty_chan_2 }.
 * Possible values for duty cycle are 0 - 1023.
 */
const int kDayState[] = { 600, 400 }; // daytime LED state
const int kNightState[] = { 0, 0 }; // nighttime LED state

/*
 * Duration (in seconds) of fade.  At the moment the only fades are sunrise and
 * sunset but this value will apply to any other fades you came up with
 */
const long kFadeDuration = 1200; // 20 minutes

long ctr;

/* hold state info */
int state_chan1, state_chan2;

/*
 * fader -- Determine output state for a given time to provide smooth fade from
 * one state to another.
 *     Args:
 *     start_time  -- time (in seconds) of start of fade
 *     start_state -- beginning state
 *     end_state   -- ending state
 *     out         -- array to update with state
 */
void fader(long start_time, const int start_state[], const int end_state[], int out[2]) {

  float per_second_delta_0 = (float) (end_state[0]-start_state[0])/kFadeDuration;
  float per_second_delta_1 = (float) (end_state[1]-start_state[1])/kFadeDuration;

  long elapsed = ctr-start_time;

  out[0] = start_state[0] + per_second_delta_0 * elapsed;
  out[1] = start_state[1] + per_second_delta_1 * elapsed;
}

// return seconds elapsed since midnight
long seconds_since_midnight() {
  time_t t = now();
  long hr = hour(t);
  long min = minute(t);
  long sec = second(t);
  long total = hr * 3600 + min * 60 + sec;
  return total;
}

// set output state
void set_state(const int state[]) {
  if (state[0] >= 0 && state[0] <= 1023) {
    Timer1.setPwmDuty(kChan0Pin, state[0]);
    state_chan1 = state[0]; }
  if (state[1] >= 0 && state[1] <= 1023) {
    Timer1.setPwmDuty(kChan1Pin, state[1]);
    state_chan2 = state[1]; }
}

/*
 * determine_state -- This is where the actual timing logic resides.  We
 * examine ctr (seconds since midnight) and then set output state accordingly.
 * Variable ctr rolls back to 0 at midnight so stages that cross midnight (ie:
 * nighttime) are broken up into two stages.
 */
void determine_state() {
  if ( ctr >= 0 && ctr < kTurnOn ) { // night
      set_state(kNightState);
  } else if ( ctr >= kTurnOn && ctr <= (kTurnOn+kFadeDuration) ) { // sunrise
    int foo[2];
    fader(kTurnOn, kNightState, kDayState, foo);
    set_state(foo);
  } else if ( ctr > (kTurnOn+kFadeDuration) && ctr < kTurnOff ) { // day
    set_state(kDayState);
  } else if ( ctr >= kTurnOff && ctr <= (kTurnOff+kFadeDuration) ) { // sunset
    int foo[2];
    fader(kTurnOff, kDayState, kNightState, foo);
    set_state(foo);
  } else if ( ctr > (kTurnOff+kFadeDuration) && ctr < 86400 ) { // night
    set_state(kNightState);
  }
}

/*
 * Utility function for pretty digital clock time output
 * From example code in Time library -- author unknown
 */
void printDigits(int digits) {
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

/*
 * Display time
 * Adapted from example code in Time library -- author unknown
 */
void digitalClockDisplay() {
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(month());
  Serial.print("/");
  Serial.print(day());
  Serial.print("/");
  Serial.print(year()); 
  Serial.println(); 
}

void setup() {
  Serial.begin(115200); // Max for Arduino Uno
  setSyncProvider(RTC.get);
  Timer1.initialize(6666); // 150Hz PWM
  pinMode(kChan0Pin, OUTPUT);     
  Timer1.pwm(kChan0Pin, 0);
  pinMode(kChan1Pin, OUTPUT);     
  Timer1.pwm(kChan1Pin, 0);
}

void loop () {
  ctr = seconds_since_midnight();
  determine_state();

  if (Serial.available() >= 5) {
    char data[4];
    for (int i=0; i<5; i++) {
      data[i] = Serial.read();
    }

    Serial.flush(); // ensure we never have more than 5 bytes buffered

    if (data[0] == 'A') { // send current time
      time_t longInt = now();
      unsigned char byteArray[4];
                
      // convert from an unsigned long int to a 4-byte array
      byteArray[0] = (int)((longInt >> 24) & 0xFF);
      byteArray[1] = (int)((longInt >> 16) & 0xFF);
      byteArray[2] = (int)((longInt >> 8) & 0XFF);
      byteArray[3] = (int)((longInt & 0XFF));
      // send time
      Serial.print("Z");
      Serial.print(byteArray[0]);
      Serial.print(byteArray[1]);
      Serial.print(byteArray[2]);
      Serial.print(byteArray[3]);
    }
    else if (data[0] == 'B') { // set time
      union u_tag {
        byte b[4];
        unsigned long ulval;
      } u;

      u.b[0] = data[4];
      u.b[1] = data[3];
      u.b[2] = data[2];
      u.b[3] = data[1];

      RTC.set(u.ulval);
      setTime(u.ulval);
      Serial.print("Z0000");
    }
    else {
      Serial.print("X0000");
    }
  }

  delay(250);

/*
  Serial.print("ctr: ");
  Serial.print(ctr); // display counter
  Serial.println(); 
  Serial.print("channel 1, 2: "); 
  Serial.print(state_chan1); 
  Serial.print(", "); 
  Serial.print(state_chan2); 
  Serial.println(); 
  digitalClockDisplay(); //display time
  Serial.println(); 
*/
}

This program uses very convoluted logic. I would not mess with it at all. What they do with foo[] is just criminal. :slight_smile:

aarg:
This program uses very convoluted logic. I would not mess with it at all. What they do with foo[] is just criminal. :slight_smile:

That does seem like an excessive amount of code just to dim lights over a 30 minute period. And that's coming from someone who's as green as a tobacco chewing 12 year old when it comes to arduino coding.

So what logic would I employ? something like this on off timer code

//NJarpa
// Arduino timer on/off setpoint.
//If you find this code,visit INSTRUCTABLES and type the title above.
//Arduino UNO + RTC 1307 + 16X2 LCD + 4 buttons


#include <LiquidCrystal.h>   //Libraries
#include <RTClib.h>
#include <Wire.h>

LiquidCrystal lcd(2, 3, 4, 5, 6, 7); //Arduino pins to lcd
//LCD 1602
//RS to 2pin, EN to 3pin, D4 to 4pin, D5 to 5pin, D6 to 6pin, D7 to 7pin
//Contrast=  10K potentiometer: 1 to Vcc, 2(center)to VO, 3 to GND
//Backlight= K to GND(with 1K resistor), A to Vcc
//Vss to GND , Vdd to +5v

RTC_DS1307 RTC;                     // define the Real Time Clock object
//RTC 1307
//SDA to analog 4,  SCL to analog 5

//You need 4 buttons like this,to enter the set points, with 10K resistor to ground (for each one)

//                  To arduino pins(8, 9, 10, 11)
//                        |
//                        |
//              switch    |   
//              _______   |         10K          
//    +5V  0-----0   0----0------/\/\/\/\-------0  to GND

const int DOWN_BUTTONon =9;        //Buttons Pins
const int UP_BUTTONon =8;
const int DOWN_BUTTONoff =11;       
const int UP_BUTTONoff =10;
const int Relay =12;             //Relay pin

//-----Variables for debouncing------//

boolean lastDownONButton = LOW;
boolean currentDownONButton = LOW;
boolean lastUpONButton = LOW;
boolean currentUpONButton = LOW;
boolean lastDownOFFButton = LOW;
boolean currentDownOFFButton = LOW;
boolean lastUpOFFButton = LOW;
boolean currentUpOFFButton = LOW;


int set_on = 12;        //The default "ON" desired time
int set_off= 12;        //The default "OFF" desired time


void setup(){
  pinMode(Relay, OUTPUT);
  
     Wire.begin();
     RTC.begin();
     lcd.begin(16, 2); // Configura lcd numero columnas y filas
     
     lcd.setCursor(0,0);  //Show "TIME" on the LCD
     lcd.print("TIME");
     lcd.setCursor(0,1);
     lcd.print("ON");    //Show "ON" on the lcd
     lcd.setCursor(6,1);
     lcd.print("OFF");  //Show "OFF" on the lcd
}
     
   //----Debouncing function for all buttons----//
boolean debounce(boolean last, int pin)
{
boolean current = digitalRead(pin);
if (last != current)
{
delay(5);
current = digitalRead(pin);
}
return current;
}
     
 

void loop(){
//--------Show current Time On LCD--------//


DateTime now = RTC.now();        // Clock call
now = RTC.now();

lcd.setCursor(5,0);                 
if(now.hour() < 10)
{
lcd.print("0");
}
lcd.print(now.hour(), DEC); //Print hour
lcd.print(':');
if(now.minute() < 10)
{
lcd.print("0");
}
lcd.print(now.minute(), DEC); //Print min
lcd.print(':');
if(now.second() < 10)
{
lcd.print("0");
}
lcd.print(now.second(), DEC); //Print sec

//----Debounce  buttons---//
currentDownONButton = debounce(lastDownONButton, DOWN_BUTTONon);
currentUpONButton = debounce(lastUpONButton, UP_BUTTONon);
currentDownOFFButton = debounce(lastDownOFFButton, DOWN_BUTTONoff);
currentUpOFFButton = debounce(lastUpOFFButton, UP_BUTTONoff);

//-----Turn down the set "ON"-----//
if (lastDownONButton== LOW && currentDownONButton == HIGH)
{
if(set_on>0){    //"ON" Set point  down never lower than 0
set_on--;        
}
else{
  lcd.setCursor(3,1);
  lcd.print("0");
}
}

//----Turn up the set "ON"----//
else if (lastUpONButton== LOW && currentUpONButton == HIGH)
{
  if(set_on<23){   //"ON" Set point up never higher than 23
  set_on++;
}
else{
  lcd.setCursor(3,1);
  lcd.print("23");
}
}

//---Print the set "ON"---//
lcd.setCursor(3,1);
if(set_on < 10){   
  lcd.print("0");
}
lcd.print(set_on);
lastDownONButton = currentDownONButton;
lastUpONButton = currentUpONButton;

//-----Turn down the set "OFF"-----//
if (lastDownOFFButton== LOW && currentDownOFFButton == HIGH)
{
if(set_off>0){    //"OFF" Set point  down never lower than 0
set_off--;
}
else{
  lcd.setCursor(10,1);
  lcd.print("0");
}
}

//----Turn up the set "OFF"----//
else if (lastUpOFFButton== LOW && currentUpOFFButton == HIGH)
{
  if(set_off<23){   //"OFF" Set point up never higher than 23
  set_off++;
}
else{
  lcd.setCursor(10,1);
  lcd.print("23");
}
}

//---Print the set "OFF"---//
lcd.setCursor(10,1);
if(set_off < 10){   
  lcd.print("0");
}
lcd.print(set_off);
lastDownOFFButton = currentDownOFFButton;
lastUpOFFButton = currentUpOFFButton;



//----Relay Function----//
if(set_on == set_off){
   digitalWrite(Relay, LOW);
}

if(set_on < set_off){
  
             if(now.hour() >= set_on && now.hour() < set_off ){             //Start
             digitalWrite(Relay, HIGH);
             }
             else if(now.hour() >= set_off) {
             digitalWrite(Relay, LOW);
             }
             else{
             digitalWrite(Relay, LOW);
             }
}
if (set_on > set_off){

            if(now.hour() >= set_on && now.hour() <= 23){                  //Start
            digitalWrite(Relay, HIGH);  
            }
            else if(now.hour() < set_off){
            digitalWrite(Relay, HIGH);
            }
            else if(now.hour() >= set_off && now.hour() < set_on){
            digitalWrite(Relay, LOW);  
            }
}

}// The End

how would I add pwm values to set on, set off and a fading function?

In my opinion you would be better off starting from scratch and writing your own program. It is often easier to do that rather than to untangle the logic of an existing program and try to add functionality to it and in addition you will learn a lot.

Do you have an RTC as assumed in the code in post #2 ?

With (I would think) 4 states, off, dim up, on and dim down, I would probably look into Switch/Case along with the RTC @UKHeliBob mentioned.

But someone with a bit more experience may have a better way to handle it.

Darn I thought it would be easy to just plug in a few lines of code... Yes, I have the DS1307 RTC and meanwell PWM dimming drivers for the LEDs.

I guess there is at least one more state I forgot about. Cloudy. The main one you were asking about. You could even go crazy and do simulated thunderstorms. All depends on how much effort you're willing to put into it. I've seen vids showing the thunderstorms. Pretty cool really.

If you're capable of retaining what you learn (unlike my old brain) this stuff is not THAT difficult to pick up. And these folks are pretty good at guidance and resolving issues in code.

There are examples in the IDE that will help you get your feet wet. In particular, there is one for dimming an LED.

For others looking for a far simpler and infinitely flexible solution:

Get an APA102C RGB or similarly powerful LED strip, a matching 5V power supply, a 1000uF capacitor and an Adafruit Trinket 5V and wire it up as per the many online instructions available.

Install the FastLED library and get the palette knife browser extension.

And then, you can chain any kind of sky, cloud, submarine, etc. gradient over time with very little code and exact timings, in case these are needed.