Fade in and out interruptor without delay

Hey guys. I made a project to fade in and fade out for wall interrutors. The idea is when you flip the switch to turn on the kitchen light, for example, it will fade in light slowly instead of turning it on immediatlly as usual. And when you turn the switch off, the arduino fades out.

The code is working perfectly for two switches, including no delay, that is, the switches work simultaneously. However, as you can see the variables and functions are repeated for each switch that is added to the code. I would like to rewrite the code, creating a variable to tell how many switches will be used and using arrays (or otherwise) to write the fadeIn and fadeOut functions just once. So, every time you call the fadeIn function it will be the same function, but it would take into account which switch was pressed and then the fadeIn function would be executed to fade only this switch.

I tried using arrays but no sucess. Any suggestion? Thank you in advance

// define directions for LED fade
#define UP 0
#define DOWN 1

//contants for leds and buttons
const int ledPin1 =  9;// the number of the 1st LED pin
const int ledPin2 =  10;// the number of the 2nd LED pin
const int bt1 = 2;
const int bt2 = 3;
 
// constants for min and max PWMs
const int minPWM1 = 0; // 0 = off, 255 = totally on
const int minPWM2 = 0;
const int maxPWM1 = 255;
const int maxPWM2 = 255;
 
// State Variable for Fade Direction
byte fadeDirection1 = UP;
byte fadeDirection2 = UP;

// Global Fade Value
// but be bigger than byte and signed, for rollover
int fadeValue1 = 0;
int fadeValue2 = 0;

// How smooth to fade?
byte fadeIncrement1 = 5;
byte fadeIncrement2 = 5;

// Variables will change:
int BUTTONstate1 = 0;
int BUTTONstate2 = 0;
 
// millis() timing Variable, just for fading
unsigned long previousFadeMillis1;
unsigned long previousFadeMillis2;
 
// How fast to increment?
int fadeInterval1 = 20;
int fadeInterval2 = 20;
 
void setup() {
  // set the digital pin as output:
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(bt1, INPUT);
  pinMode(bt2, INPUT);
  
  // put pwmLED into known state (off)
  analogWrite(ledPin1, fadeValue1); 
  analogWrite(ledPin2, fadeValue2); 
}

// function fade in for 1st led
void FadeIn1(unsigned long thisMillis1) {
  
  // is it time to update yet?
  // if not, nothing happens
  if (thisMillis1 - previousFadeMillis1 >= fadeInterval1) {
    
      // yup, it's time!
      fadeValue1 = fadeValue1 + fadeIncrement1;  
      if (fadeValue1 >= maxPWM1) {
        
        // At max, limit and change direction
        fadeValue1 = maxPWM1;
        fadeDirection1 = DOWN;
      }
    // Only need to update when it changes
    analogWrite(ledPin1, fadeValue1);  
 
    // reset millis for the next iteration (fade timer only)
    previousFadeMillis1 = thisMillis1;
  }
}

// function fade out for 1st led
void FadeOut1(unsigned long thisMillis1) {
  
  // is it time to update yet?
  // if not, nothing happens
  if (thisMillis1 - previousFadeMillis1 >= fadeInterval1) {
    
      // yup, it's time!
      fadeValue1 = fadeValue1 - fadeIncrement1;  
      if (fadeValue1 <= minPWM1) {
        
        // At max, limit and change direction
        fadeValue1 = minPWM1;
        fadeDirection1 = UP;
      } 
    // Only need to update when it changes
    analogWrite(ledPin1, fadeValue1);  
 
    // reset millis for the next iteration (fade timer only)
    previousFadeMillis1 = thisMillis1;
  }
}

// function fade in for 2nd led
void FadeIn2(unsigned long thisMillis2) {
  
  // is it time to update yet?
  // if not, nothing happens
  if (thisMillis2 - previousFadeMillis2 >= fadeInterval2) {
  
      // yup, it's time!
      fadeValue2 = fadeValue2 + fadeIncrement2;  
      if (fadeValue2 >= maxPWM2) {
        
        // At max, limit and change direction
        fadeValue2 = maxPWM2;
        fadeDirection2 = DOWN;
      }
    // Only need to update when it changes
    analogWrite(ledPin2, fadeValue2);  
 
    // reset millis for the next iteration (fade timer only)
    previousFadeMillis2 = thisMillis2;
  }
}

// function fade out for 2nd led
void FadeOut2(unsigned long thisMillis2) {
  
  // is it time to update yet?
  // if not, nothing happens
  if (thisMillis2 - previousFadeMillis2 >= fadeInterval2) {
    
      // yup, it's time!
      fadeValue2 = fadeValue2 - fadeIncrement2;  
      if (fadeValue2 <= minPWM2) {
        
        // At max, limit and change direction
        fadeValue2 = minPWM2;
        fadeDirection2 = UP;
      } 
    // Only need to update when it changes
    analogWrite(ledPin2, fadeValue2);  
 
    // reset millis for the next iteration (fade timer only)
    previousFadeMillis2 = thisMillis2;
  }
}
void loop() {

  // to know wich button is pressed
  BUTTONstate1 = digitalRead(bt1);
  BUTTONstate2 = digitalRead(bt2);  

  if(BUTTONstate1 == HIGH){
  
    // get the current time, for this time around loop
    // all millis() timer checks will use this time stamp
    unsigned long currentMillis1 = millis(); 
    FadeIn1(currentMillis1);
  }
  if(BUTTONstate1 == LOW){
  
    // get the current time, for this time around loop
    // all millis() timer checks will use this time stamp
    unsigned long currentMillis1 = millis();  
    FadeOut1(currentMillis1);
  }
  if(BUTTONstate2 == HIGH){
  
    // get the current time, for this time around loop
    // all millis() timer checks will use this time stamp
    unsigned long currentMillis2 = millis();
    FadeIn2(currentMillis2);
  }
  if(BUTTONstate2 == LOW){
  
    // get the current time, for this time around loop
    // all millis() timer checks will use this time stamp
    unsigned long currentMillis2 = millis();  
    FadeOut2(currentMillis2);
  }
}

can you post a schematic?

You might consider using a structure or class to represent each "fader" object -- which consists of a PWM output pin and a button/switch input. Declare all the Fader objects you need and then form an array of pointers to these objects. You can then leverage the array and update one object each pass of loop. For example:

#define NUM_FADES   2

//contants for leds and buttons
const int ledPin1 =  9;// the number of the 1st LED pin
const int ledPin2 =  10;// the number of the 2nd LED pin
const int bt1 = 2;
const int bt2 = 3;
 
class Fader
{
    public:
        Fader( uint8_t pinFade, uint8_t pinButton )
        {
            _pinFade = pinFade;
            _pinButton = pinButton;
            
        }//Fader

        void begin( void )
        {            
            pinMode( _pinFade, OUTPUT );
            analogWrite( _pinFade, _fadePWMValue );
            
            pinMode( _pinButton, INPUT_PULLUP );            
            
        }//begin
        
        void setFadeInterval( uint32_t interval )
        {
            _fadeInterval = interval;
            
        }//setFadeInterval
        
        void setFadeIncrement( uint8_t increment )
        {
            _fadeIncrement = increment;
            
        }//setFadeIncrement

        void setFadeMaxMinPWM( uint8_t minPWM, uint8_t maxPWM )
        {
            if( maxPWM > minPWM )
            {
                _fadeMaxPWM = maxPWM;
                _fadeMinPWM = minPWM;
                
            }//if
            
        }//setFadeMaxMinPWM

        void update( void )
        {
            _timeNow = millis();
            if( (_timeNow - _timeFade) < _fadeInterval )
                return;

            _timeFade = _timeNow;
                
            if( digitalRead( _pinButton ) == HIGH )
            {                                                   
                //has pwm reached maximum value?
                if( _fadePWMValue >= _fadeMaxPWM )
                    return;
                
                if( (int16_t)_fadePWMValue + _fadeIncrement >= (int16_t)_fadeMaxPWM )                
                    _fadePWMValue = _fadeMaxPWM;
                else
                    _fadePWMValue += _fadeIncrement;

                analogWrite( _pinFade, _fadePWMValue );
                
            }//if
            else
            {
                //has pwm reached minimum value?
                if( _fadePWMValue <= _fadeMinPWM )
                    return;
                                    
                if( (int16_t)_fadePWMValue - _fadeIncrement <= (int16_t)_fadeMinPWM )                
                    _fadePWMValue = _fadeMinPWM;
                else
                    _fadePWMValue -= _fadeIncrement;
                
                analogWrite( _pinFade, _fadePWMValue );                
                
            }//else
                            
        }//update
        
    private:
        uint8_t     _pinFade;
        uint8_t     _pinButton;
        //
        uint8_t     _fadeMaxPWM = 255;
        uint8_t     _fadeMinPWM = 0;
        uint8_t     _fadePWMValue = 20;
        //
        uint8_t     _fadeIncrement = 5;
        uint32_t    _fadeInterval = 20ul;
        uint32_t    _timeFade = 0ul;
        uint32_t    _timeNow = 0ul;
               
};//class Fader

Fader fadeLED1( ledPin1, bt1 );
Fader fadeLED2( ledPin2, bt2 );

Fader *grFades[NUM_FADES] = 
{
    &fadeLED1
    &fadeLED2
    
};
 
void setup() 
{
    Serial.begin(115200);
    for( uint8_t i=0; i<NUM_FADES; i++ )
    {
        grFades[i]->begin();
        grFades[i]->setFadeInterval( 20ul );
        grFades[i]->setFadeMaxMinPWM( 0, 255 );
        grFades[i]->setFadeIncrement( 5ul );
        
    }//for
    
}//setup

void loop() 
{
    static uint8_t
        indx = 0;
        
    grFades[indx++]->update();
    if( indx == NUM_FADES )
        indx = 0;
        
}//loop

This compiles but isn't tested. I'm not really good at class stuff and probably screwed this up but I think this conveys the idea I'm trying to get across.

1 Like

Well I think a timer including the dim function as methode and a structured array containing all relevant data should help to design a smart solution.

1 Like

consider

struct Device {
    byte            butPin;
    byte            outPin;
    unsigned long   msecRate;
    const char     *desc;

    int             outPwm;
    unsigned long   msecLst;
};

Device devices [] = {
    { A1, 10,  10,  "led1" },
    { A2, 11,  20,  "led2" },
};
#define N_DEV   (sizeof(devices)/sizeof(Device))

enum { ButOff = HIGH, ButOn = LOW };
enum { DevOff = 255,  DevOn = 0 };

// -----------------------------------------------------------------------------
void
loop ()
{
    unsigned long msec = millis ();

    Device *p = devices;
    for (unsigned n = 0; n < N_DEV; n++, p++)  {
        byte but = digitalRead (p->butPin);

        if ( (msec - p->msecLst) > p->msecRate)  {
            p->msecLst = msec;

            if (ButOn == but && p->outPwm != DevOn)
                analogWrite (p->outPin, p->outPwm--);
            else if (ButOff == but && p->outPwm < DevOff)
                analogWrite (p->outPin, ++(p->outPwm));
        }
    }
}

// -----------------------------------------------------------------------------
void setup () {
    Serial.begin (9600);

    Device *p = devices;
    for (unsigned n = 0; n < N_DEV; n++, p++)  {
        pinMode      (p->butPin,  INPUT_PULLUP);
    }
}
1 Like

@Blackfin and @paulpaulson
I estimate that the suggestions you made were way above the head of daniel.

In this shortage as you have described it I estimate it is not understandable for daniel.

sure it can be done with classes, structures and a lot more variants. But as long as you don't provide a very well tested and documented library with examples on how to use your library it would be additional work without orientation.

OK back to daniel:
Your basic idea of having a single function for fade-in / fade-out is good.
Also having variables (eventually as array ) for each light is a good idea.

You have to clarify what belongs to a "general pattern" and what has to be individual.

With general pattern I mean what are things that are the same for each button / and the light that is controlled by this button.
reading in the button's state
and if button is pressed start fade-in / fade-out

This has to be done for each button-light-pair

What is varying is
the IO-pin the button is connected to,
the IO-pin that creates the PWM-signal

So these two things must be parameters that are handed over to the functions "fade-in" / "fade-out"

Your functions already use a single parameter.
functions can have multiple parameters even hundreds if you want.
So one way to realise this is to have multiple parameters

another way is to have a set of variables that are bundled together because they belong together
each buttin/light needs
IO-pin-number for button-pin
IO-pin-number for PWM-pin
a variable for previousMillis
a variable for min/maxPWM
fadeDirection
fadeValue
fadeIncrement
fadeintervall.

quite a lot of variables

to avoid writing

const int bt1 = 2;
const int bt2 = 3;
... 
// constants for min and max PWMs
const int minPWM1 = 0; // 0 = off, 255 = totally on
const int minPWM2 = 0;
...
const int maxPWM1 = 255;
const int maxPWM2 = 255;

arrays can be used.

let's assume you want to have 10 buttons
const int btn[10]

the sharp-edged brackets [10] define it as array with 10 elements

your if-condition

  if(BUTTONstate1 == LOW){

becomes

  if(btn[0] == LOW){

and as you want to minimise writing code you don't write

  if(btn[0] == LOW){
  if(btn[1] == LOW){
  if(btn[2] == LOW){
  if(btn[3] == LOW){
..
  if(btn[9] == LOW){

you write a loop

for (i = 0 ; i < 10; i++ ) {
  if(btn[i] == LOW){

now the for-loop counts up from 0 to 9 and does check each variable (which is an element of the array)

and this principle is applied to each variable.

This means the numbering
const int minPWM1 = 0;
const int minPWM2 = 0;
const int minPWM3 = 0;
...
const int minPWM50 = 0;

is moved from the "code-writing"-level to the "code-execution-level"
by using arrays where an index-variable does the "numbering"

And there is another thing that is useful to reduce writing code even more
structs

you can pack together multiple variables into one struct
and then create an array of this struct.

struct myBtnLightStruct
{
  int ledPin;
  int btn;
  int minPWM; 
  int maxPWM;
....
};

typedef struct myBtnLightStruct myBtnLight_t;

myBtnLight_t myChannel[10];

and then accessing each variable inside this struct is done by this notation

myChannel[0].ledPin = 9
myChannel[0].Btn = 2;
etc.

for your second button

myChannel[1].ledPin = 10
myChannel[1].Btn = 3;
etc.

What I have written so far gives you a rough picture of the principles
andI'm pretty sure that it will take some time to think about it, to re-read it
doing some prelimary tests

NOT with your full code !

but with a much smaller demo-example to really grasp how it works

and then coming back with specific questions to the forum while beeing in this learning-process

best regards Stefan

For the basic coding in C++ no libaries are needed. Only the unterstanding of the brilliant instruction set to code smart sketches. That´s all you need.
Have a nice day and enjoy coding in C++.

1 Like

Guys you are amazing. This is my first forum post and I didn't expect a help so quickly.
I'm really new in this area. I'm studying computer science in my third semester. Still very early on, but yes I've heard about object-oriented programming, but I still don't understand it very well.
I really appreciate your help. As mentioned, I will take some time to digest the information and test it until I get it. As soon as I get it I'll be back to let them know the progress.

Thank you :slight_smile:

I estimate that the suggestions you made were way above the head of daniel.

This is possible but if so then it's likely this is too:

struct myBtnLightStruct
{
  int ledPin;
  int btn;
  int minPWM; 
  int maxPWM;
....
};

and

typedef struct myBtnLightStruct myBtnLight_t;

I did end up testing my code on my 2650 using the built-in LED and it worked.

Perhaps the class approach just looks complicated because it includes some fluff like class member functions setFadeInterval, setFadeIncrement and setFadeMaxMinPWM instead of just using constants.

If the OP is unsure of classes he could always reference the Arduino tutorial on the subject.

object oriented programming doesn't require classes

1 Like

a little less difficult to understand because it takes the variable-names of the TO's code and has quite a lot of text around it that tries to explain it.

It doesn't require a class but a class is one way to do it, which is why I wrote "...structure or class to represent each "fader" object..."

Hello
Here comes my class-less but OOP oriented proposal sketch for your fader project and OPP studies.
It has been successfully tested for one block of switches and LEDs. You can extend the sketch to more I/O´s by changing the declarations of I/O pins and adding the related entries to the array. Thats all and very easy to use. Therefore no changes inside the coding are needed.

/* BLOCK COMMENT
  ATTENTION: This Sketch contains elements of C++.
  https://www.learncpp.com/cpp-tutorial/
  https://forum.arduino.cc/t/fade-in-and-out-interruptor-without-delay/921917
*/
#define ProjectName "Fade in and out interruptor without delay"
// hardware and timer settings
enum {Blk1};
constexpr byte SwitchPin[] {A0};    // portPin o---|button|---GND
constexpr byte LedPin[] {3};          // portPin o---|220|---|LED|---GND
constexpr unsigned long DimStep {10};
constexpr unsigned long DebounceTime {20};
// VARIABLE DECLARATION AND DEFINITION
unsigned long currentTime;
struct TIMER {              // has the following members
  unsigned long duration;   // memory for interval time
  bool repeat_;             // control for blinking
  bool control_;            // control for start/stop
  unsigned long stamp;      // memory for actual time
};
enum {DimUp, DimOut};
struct FADE {
  byte ledPin;
  byte buttonPin;
  TIMER debounce;
  bool statusQuo;
  TIMER dimmer;
  byte dimNow;
  bool  dimDir;
  byte dimMax;
  byte dimMin;
};
FADE faders[] {
  {LedPin[Blk1], SwitchPin[Blk1], DebounceTime, true, true, 0, false, DimStep, true, false, 0, 0, DimUp, 255, 0},
};
// ------------------ USER FUNCTIONS ---------------
void startTimer(TIMER &timer) {
  timer.control_ = true;
  timer.stamp = currentTime;
}
void stoppTimer(TIMER &timer) {
  timer.control_ = false;
}
bool checkTimer(TIMER & time_) {  // generic time handler using TIME struct
  if (currentTime - time_.stamp >= time_.duration && time_.control_) {
    if (time_.repeat_) time_.stamp = currentTime;
    else time_.control_ = false;
    return true;
  } else return false;
}
//--------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  Serial.println(F("."));
  Serial.print(F("File   : ")), Serial.println(__FILE__);
  Serial.print(F("Date   : ")), Serial.println(__DATE__);
  Serial.print(F("Project: ")), Serial.println(ProjectName);
  pinMode (LED_BUILTIN, OUTPUT);  // used as heartbeat indicator
  for (auto fader : faders) pinMode(fader.ledPin, OUTPUT), pinMode(fader.buttonPin, INPUT_PULLUP), digitalWrite(fader.ledPin, HIGH);
  delay (1000); // check LED hardware
  for (auto fader : faders) digitalWrite(fader.ledPin, LOW);
}
void loop () {
  currentTime = millis();
  digitalWrite(LED_BUILTIN, (currentTime / 500) % 2);
  for (auto &fader : faders) {
    if (checkTimer(fader.debounce)) {
      bool stateNew = !digitalRead(fader.buttonPin);
      if (fader.statusQuo != stateNew) {
        fader.statusQuo = stateNew;
        stateNew ? fader.dimDir = DimUp : fader.dimDir = DimOut;
        startTimer(fader.dimmer);
      }
    }
  }
  for (auto &fader : faders) {
    if (checkTimer(fader.dimmer)) {
      fader.dimDir ? fader.dimNow-- : fader.dimNow++;
      analogWrite(fader.ledPin, fader.dimNow);
      if ((fader.dimNow == fader.dimMax) || (fader.dimNow == fader.dimMin)) stoppTimer(fader.dimmer);
    }
  }
}

Have a nice day and enjoy coding in C++.

Nice. But "oH gOd Its ToO CoMplIcAtEd fOr tHe Op" - someone here, inevitably.

1 Like

Guys, thanks to your help I managed to advance the code. It's not ideal yet because I haven't used structs, but at least I was able to use the arrays and thus a single function for all the leds reusing a lot of code. I will keep studying to understand structs and try to use it.

// define directions for LED fade
#define UP 0
#define DOWN 1

//set here how many leds you will use (max 6)
const int ledQty = 4;

//contants for leds and buttons
const int ledPin[] = {3, 5, 6, 9, 10, 11};// all PWM arduino pins
const int bt[] = {2, 4, 7, 8, 12, 13};
 
// constants for min and max PWMs
const int minPWM[] = {0, 0, 0, 0, 0, 0}; // 0 = off, 255 = totally on
const int maxPWM[] = {255, 255, 255, 255, 255, 255};
 
// State Variable for Fade Direction
byte fadeDirection[] = {UP, UP, UP, UP, UP, UP};

// Global Fade Value
// but be bigger than byte and signed, for rollover
int fadeValue[] = {0, 0, 0, 0, 0, 0};

// How smooth to fade?
byte fadeIncrement[] = {5, 5, 5, 5, 5, 5};

// Variables will change:
int BUTTONstate[] = {0, 0, 0, 0, 0, 0};
 
// millis() timing Variable, just for fading
unsigned long previousFadeMillis[] = {0, 0, 0, 0, 0, 0};
unsigned long currentMillis[] = {0, 0, 0, 0, 0, 0};
 
// How fast to increment?
int fadeInterval[] = {20, 20, 20, 20, 20, 20};
 
void setup() {
  // set the digital pin as output:
  for (int i = 0; i < ledQty; i++){
    pinMode(ledPin[i], OUTPUT);
    pinMode(bt[i], OUTPUT);

    // put pwmLED into known state (off)
    analogWrite(ledPin[i], fadeValue[i]);
  }
}

// function fade in
void FadeIn(unsigned long thisMillis, int i) {
  
  // is it time to update yet?
  // if not, nothing happens
  if (thisMillis - previousFadeMillis[i] >= fadeInterval[i]) {
    
      // yup, it's time!
      fadeValue[i] = fadeValue[i] + fadeIncrement[i];  
      if (fadeValue[i] >= maxPWM[i]) {
        
        // At max, limit and change direction
        fadeValue[i] = maxPWM[i];
        fadeDirection[i] = DOWN;
      }
    // Only need to update when it changes
    analogWrite(ledPin[i], fadeValue[i]);  
 
    // reset millis for the next iteration (fade timer only)
    previousFadeMillis[i] = thisMillis;
  }
}

// function fade out
void FadeOut(unsigned long thisMillis, int i) {
  
  // is it time to update yet?
  // if not, nothing happens
  if (thisMillis - previousFadeMillis[i] >= fadeInterval[i]) {
    
      // yup, it's time!
      fadeValue[i] = fadeValue[i] - fadeIncrement[i];  
      if (fadeValue[i] <= minPWM[i]) {
        
        // At max, limit and change direction
        fadeValue[i] = minPWM[i];
        fadeDirection[i] = UP;
      } 
    // Only need to update when it changes
    analogWrite(ledPin[i], fadeValue[i]);  
 
    // reset millis for the next iteration (fade timer only)
    previousFadeMillis[i] = thisMillis;
  }
}

void loop() {

  for (int i = 0 ; i < ledQty; i++ ) {
      // to know wich button is pressed
      BUTTONstate[i] = digitalRead(bt[i]);
  
      if(BUTTONstate[i] == HIGH){
          // get the current time, for this time around loop
          // all millis() timer checks will use this time stamp
          currentMillis[i] = millis(); 
          FadeIn(currentMillis[i], i);
      }

      if(BUTTONstate[i] == LOW){
          // get the current time, for this time around loop
          // all millis() timer checks will use this time stamp
          currentMillis[i] = millis(); 
          FadeOut(currentMillis[i], i);
      }
  }
}

Follow a video from project.