Two LED Fading Sequence Controlled by Switch on Analog Input

I am new to programming in general, and this is my first time on the forum, so advanced apologies for likely generating a few face-palms! :slight_smile:

I am working on a lighting sequence for my Star Trek USS Enterprise model, and need a toggle switch on A-1 to initiate a slow fade on at PWM-9. Upon PWM-9 reaching full brightness, I would like PWM-10 to then fade on as PWM-9 fades off.

PWM-10 should remain at full brightness until the toggle switch is turned off, at which point both PWM’s fade off.

As it stands, the sequence begins properly, but PWM-9 continues to cycle on and off as long as the switch is on, and PWM-10 won’t fade off at all.

I have found numerous topics and tutorials which address bits and pieces of my dilemma but nothing that I have been able to integrate successfully. Any help would be greatly appreciated!

AnalogInput_to_analog_Output.ino (1.76 KB)

if (ledPin9 == 0);

^^ This doesn't do anything.
Lose the semicolon.
If you then need more than 1 statement to be conditional, put { } around the block.
Edit to add: Also, ledPin9 = 9. It will never be 0.

for (int x = 0; x >= 255; x -= 1)

^^ This doesn't do anything.
You start x at zero, so it will never meet the condition of x >= 255.

Thanks, Jobi-Wan! I’ve gotten the first item deleted, and nothing in the sequence has changed. As to the second part, I think I need to be tracking the value of the PWM outputs in order to fade them back to zero once the switch is off. Any suggestions?

SteveS467:
Thanks, Jobi-Wan! I've gotten the first item deleted, and nothing in the sequence has changed. As to the second part, I think I need to be tracking the value of the PWM outputs in order to fade them back to zero once the switch is off. Any suggestions?

You can start x at 255 and check that it is >= zero. This is what you do in the 3rd loop in the if-block, but not in the else-block. Or you can loop x from 0 to 255 like you do earlier, but then use (255 - x) as the value.

Also, the 3 loops in the if all have delays. The one in the else does not.

Sorry, the PWM’s are now cycling up and down with the switch off. I think I’m missing some way to break the cycle, but haven’t found the key yet. I’ll keep searching on my end. I’ve attached the current iteration with my errant corrections, LOL.

AnalogInput_to_analog_Output.ino (1.76 KB)

SteveS467:
the PWM’s are now cycling up and down with the switch off.

How have you wired this switch? Do you have a pull-up or pull-down resistor also attached to A0? Is it a spdt switch connected to 5V, 0V and A0? Or is A0, as I suspect, floating when the switch is in the off position?

Also, why are you using analogRead() to check a switch? Why not use digitalRead()?

PWM-10 should remain at full brightness until the toggle switch is turned off, at which point both PWM’s fade off.

According to your description, PWM-9 is already off, once PWM-10 has reached full brightness, so when the switch is turned off, only PWM-10 can fade off.

Try this, with your switch wired between A0 and 0V (no pull-up resistor needed, the internal one is used):

int Input1 = A0;    // select the input pin for the switch
int ledPin9 = 9;     // select the pin for the white LED
int ledPin10 = 10;    // select the pin for the blue LED
const int threshold = 150;  // variable to store the value coming from the sensor

void setup() {
  // declare the ledPin as an OUTPUT:
  pinMode(ledPin9, OUTPUT);
  pinMode(ledPin10, OUTPUT);
  pinMode(Input1, INPUT_PULLUP);
}

void loop() {

  while (digitalRead(Input1) == HIGH);

  for (int x = 0; x <= 255; x += 1) {
    analogWrite(ledPin9, x);
    delay(30);
  }
  for (int x = 0; x <= 255; x += 1) {
    analogWrite(ledPin10, x);
    analogWrite(ledPin9, 255 - x);
    delay(20);
  }

  while (digitalRead(Input1) == LOW);
  
  for (int x = 255; x >= 0; x -= 1) {
    analogWrite (ledPin10, x);
    delay(10);
  }
}

SteveS467:
haven't found the key yet.

Your loop() function gets called over and over. Hence the name.
You only want to do something when the state changes: when the potmeter reading goes from below to above or from above to below the threshold.
In your loop(), compare the new reading to the previous reading (from the previous loop, so you have to store it) and if it has crossed the threshold since the last loop, play the appropriate fade-up/down sequence.

Paul,

That's definitely what I'm looking for!

I have a pull-down resistor on A0, with a SPDT toggle switch wired to the 5V terminal. I'm stuck using the analog inputs because all of the digital points will be occupied driving various time-delay outputs for lighting and sound sequences. So far, this lighting effect has been the only real head-scratcher for me.

The only change I can think of is that I would like the sequence to fade off at any point when A0 goes low. The model is mounted on a base that resembles a "Star Trek: The Motion-Picture" era control panel, and most of the enable points are toggle switches (and a few slide potentiometers). I expect kids and impatient adults will be playing with the controls and probably won't wait for the sequence to cycle completely before shutting it off and moving on.

I greatly appreciate your help, and will attempt to make the remaining changes myself, and integrate this into the rest of the program!

Thanks again,

Steve

Most analog inputs can be treated as digital inputs/outputs on most types of Arduino (as example, analog inputs A6 & A7 on a Nano 3 cannot be used as digital inputs/outputs, but A0 to A5 can, as can all on Uno).

So now you are saying that you need to react to the switches during fading. That's a little harder but i will help tomorrow evening if no-one else has got you sorted before then.

Paul, I hadn’t come across that trick with the analog inputs; that will make the wiring easier!

Jobi-Wan, thank for your help in understanding the logic as well, and challenging me to think it through.

Sincerely,

Steve

SteveS467:
Paul, I hadn't come across that trick with the analog inputs; that will make the wiring easier!

Not sure what you mean Steve. If you mean using INPUT_PULLUP and so not needing external pull-up/down resistors, that can be done on any digital input, including any analog input that can also be used as a digital input.

If you use INPUT_PULLUP, that means that your pin goes high when it is not connected. A switch can set it low by connecting it to ground.

If you want to be able to ‘change your tune’ at any moment, the best way to do that is to get rid of your for-loops and delays:

Whenever your state changes, you mark the time at which the state changes, and you decide which sequence should become ‘active’.

In your loop(), you look at the current time and calculate how long ago your state changed.
If the active sequence is ‘fading up’ and the time since last state change is 500 milliseconds, then from that you can calculate the state of your LEDs.
(E.g.: If you want your fade to be maximum 3 seconds from off to full, then 500 msec is 1/6 brightness.)

void loop()
{
  read the new button state from hardware;
  
  if (the new button state is not the sam as on the last loop)
  {
    state change time = current time;
    sequence = the sequence that goes with the new state;
    remember button state, so we can compare it on the next loop;
  }

  // Do you timing like this, instead of using delays
  time into sequence = current time - state change time;

  switch (sequence)
  {
    case fade_up_seq:
      // LED 1 fades up in 1 second, then stays up
      if (time into sequence < 1000)
        set LED brightness (led1, time into sequence / 4);
      else
        set LED brightness (led1, full brightness);

      // LED2 fades up in 500 msec, then down in 500 msec, then up in 500 msec, then stays up
      if (time into sequence < 500)
        set LED brightness (led2, time into sequence / 2);
      else if (time into sequence < 1000)
        set LED brightness (led2, 500 - (time into sequence / 2));
      else if (time into sequence < 1500)
        set LED brightness (led2, (time into sequence / 2) - 500);
      else
        set LED brightness (led2, full brightness);
      break;

    case fade_down_seq:
      // LED 1 fades down in 1 second
      if (time into sequence < 1000)
        set LED brightness (led1, 250 - time into sequence / 4);
      else
        set LED brightness (led1, off);

      // LED 2 fades down in half a second
      if (time into sequence < 500)
        set LED brightness (led1, 250 - time into sequence / 2);
      else
        set LED brightness (led1, off);
      break;

    // You can add more sequences if you can read more than 2 states from hardware
    case warp_speed_seq:
      break;

    case shields_up_seq:
      break;
  }

  if (you want to do other stuff)
  {
    you can do that here; // since playing LED sequences does not block your loop anymore.
  }
}

Basically: “Given that we started playing sequence X at time Y, and time is now Z, set the LEDs accordingly.”

SteveS467:
The only change I can think of is that I would like the sequence to fade off at any point when A0 goes low.

Like this?

int Input1 = A0;    // select the input pin for the switch
int ledPin9 = 9;     // select the pin for the white LED
int ledPin10 = 10;    // select the pin for the blue LED
const int threshold = 150;  // variable to store the value coming from the sensor
int level9 = 0;
int level10 = 0;

void setup() {
  // declare the ledPin as an OUTPUT:
  pinMode(ledPin9, OUTPUT);
  pinMode(ledPin10, OUTPUT);
  pinMode(Input1, INPUT_PULLUP);
}

void loop() {

  while (digitalRead(Input1) == HIGH);

  while (level9 <= 255 && digitalRead(Input1) == LOW) {
    analogWrite(ledPin9, level9++);
    delay(30);
  }
  while (level10 <= 255 && digitalRead(Input1) == LOW) {
    analogWrite(ledPin10, level10++);
    analogWrite(ledPin9, level9--);
    delay(20);
  }

  while (digitalRead(Input1) == LOW);
  
  while (digitalRead(Input1) == HIGH) {
    if (level9 > 0) analogWrite (ledPin9, level9--);
    if (level10 > 0) analogWrite (ledPin10, level10--);
    delay(10);
  }
}

That final sequence works great, Paul! I also think Jobi-Wan has hit the nail on the head in regard to what I am trying to accomplish by suggesting the elimination of “for loops and delays.”

When I tried to integrate the sequence into my partially written code, I encountered interference between processes, so I stopped to create a “sequence of operation,” which is attached, along with the panel layout, which may provide a visual of what I am trying to create.

Here’s the code I have so far (incorporating Paul’s latest sequence). Please let me know if you would suggest moving this to another section of the forum, too:

int Input1 = A0;//Main Power Switch
int Input2 = A1;//Array Switch
int Input3 = A2;//Impulse Enable Switch
int Input4 = A3;//Warp Enable Switch
int Input5 = A4;//Phaser Fire Button
int Input6 = A5;//Photon Fire Button

int ledPin2 = 2;//Digital Output Time Delay for Weapons Lighting/Sound Effects
int ledPin3 = 3;//PWM Output to Impulse LEDs
int ledPin4 = 4;//Digital Output for Gravity Light Delay
int ledPin5 = 5;//PWM Output not used
int ledPin6 = 6;//PWM Output to Warp LEDs
int ledPin7 = 7;//Digital Output for Life Support status LED
int ledPin8 = 8;//Digital Output for Transporter status LED
int ledPin9 = 9;//PWM Output to Array White LED
int ledPin10 = 10;//PWM Output to Array Blue LEDs
int ledPin11 = 11;//Digital Output not used
int ledPin12 = 12;//Digital Output for Navigation Delay
int ledPin13 = 13;//Digital Output for Weapons Arm LED
const int threshold = 150;
int level9 = 0;
int level10 = 0;

void setup() {

  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
  pinMode(ledPin4, OUTPUT);
 
  pinMode(ledPin6, OUTPUT);
  pinMode(ledPin7, OUTPUT);
  pinMode(ledPin8, OUTPUT);
  pinMode(ledPin9, OUTPUT);
  pinMode(ledPin10, OUTPUT);
  
  pinMode(ledPin12, OUTPUT);
  pinMode(ledPin13, OUTPUT);
  pinMode (A0, INPUT_PULLUP);
  pinMode (A1, INPUT_PULLUP);
  pinMode (A2, INPUT_PULLUP);
  pinMode (A3, INPUT_PULLUP);
  pinMode (A4, INPUT_PULLUP);
}

void loop() {

  // put your main code here, to run repeatedly:
  // read the value of the input:

  // if the analog value is high enough, turn on the LED:
    while (digitalRead(Input1) == HIGH);//Detects Main PowerSwitch on
    digitalWrite(ledPin4, HIGH);//Gravity LED
    delay(100);//.1 second delay
    digitalWrite(ledPin7, HIGH);//Life Support LED
    delay(200);//.2 second delay
    digitalWrite(ledPin8, HIGH);//Transporter LED
    delay(300);//.3 second delay
    digitalWrite(ledPin12, HIGH);//Navigation LED & power to beacon, spotlights, deflector array switches, & propulsion switches
    delay(400);//.4 second delay 
    digitalWrite(ledPin13, HIGH);//Power to Weapons Arm Switch
    delay(500);//.5 second delay 
    digitalWrite(ledPin2, HIGH);//Power to Arm switch. 
    delay(600);//.6 second delay 
     
  while (digitalRead(Input2) == HIGH);

  while (level9 <= 255 && digitalRead(Input2) == LOW) {
    analogWrite(ledPin9, level9++);
    delay(30);
  }
  while (level10 <= 255 && digitalRead(Input2) == LOW) {
    analogWrite(ledPin10, level10++);
    analogWrite(ledPin9, level9--);
    delay(30);
  }

  while (digitalRead(Input2) == LOW);
  
  while (digitalRead(Input2) == HIGH) {
    if (level9 > 0) analogWrite (ledPin9, level9--);
    if (level10 > 0) analogWrite (ledPin10, level10--);
    delay(30);
  
    while (digitalRead(Input1) == LOW);//Detects Main PowerSwitch off
    digitalWrite(ledPin4, LOW);
    digitalWrite(ledPin7, LOW);
    digitalWrite(ledPin8, LOW);
    digitalWrite(ledPin12, LOW);
    digitalWrite(ledPin13, LOW);
    digitalWrite(ledPin2, LOW);
  }

}

USS Enterprise Arduino Lighting Sequence.doc (25.5 KB)

Control Panel 5.doc (221 KB)

SteveS467:

  // if the analog value is high enough, turn on the LED:

while (digitalRead(Input1) == HIGH);//Detects Main PowerSwitch on
 digitalWrite(ledPin4, HIGH);//Gravity LED

This does not do what you think it does. (Or at least, your comment is wrong.)
The ‘while’-statement with the semicolon at the end says:

As long as the input remains high, [u]do nothing[/u].

It keeps spinning in an empty loop until the input is not high, then it continues on to digitalWrite() etc…

Edit to add: About that semicolon, because initially you had the mistake with the semicolon after your ‘if’ statement…

The syntax is:

if (condition)
  statement;

if (condition)
{
  multiple;
  statements;
}

// ..but this:

if (condition); // <-- note the semicolon

// is the exact same as this:

if (condition) {} // <-- empty

So anything that comes after it is outside the if.
Same goes for while (condition) { statements; } and for (init;condition;step) { statements; }

If you’re not used to reading/writing code, this is easy to overlook.
The auto-formatting feature can help you spot these, as your statement will have the wrong indentation.

SteveS467:
[…] the elimination of “for loops and delays.”

… and while-loops, for that matter :wink:

Jobi-Wan, thank you for the clarification!

I will have a little time to study and experiment over the weekend, so I'll post an update as soon as I can.

Ok, watch out, I'm back again. :slight_smile:

Thanks to Paul and Jobi-Wan's kind assistance and a day of digging through various forums, I understand much better how to integrate the various input/output sequences, so I have decided to narrow this down to one simple (I hope) question. Here goes:

Is it possible to use a sine/cosine function to fade a PWM output from 0 to 255 without delay, then follow with the reverse process back to 0? If I have that, I think I can integrate it properly into the rest of the program.

I'll settle for a simple fade-up and back down if it's impractical, but I kinda' like the look of that wave function.

Hi, yes that’s quite possible. Rather than going ahead and building it into your larger program, i suggest you write a small, new test sketch, with one led, to trial how it looks first. I have a feeling you may be dissapointed that it does not look as different to a linear fade as you think it will. But let’s see. Its ok to use delay() in your test sketch.

Arduino C has a sine function built in - sin(). It uses float variables, so it will be slow compared to the integer maths you have used so far, but i don’t think you will notice the slowness in a program for fading leds, unless you have a lot of leds all independently using sine for their fading. Even then, there will be ways to speed up the calculations.

Whats more important is to understand that the sine function’s parameter is in radians, not degrees. One full cycle is 2 * Pi. The result will be between -1.0 and +1.0 and you will want to convert -1.0 to zero and +1.0 to 255 for use with analogWrite.

SteveS467:
Is it possible to use a sine/cosine function to fade a PWM output from 0 to 255 without delay, then follow with the reverse process back to 0?

That is getting unnecessarily complex, and fails to take into account the logarithmic repose of the eye to light.

That is to say, small changes in the value at higher light levels (250 to 255) are not even noticeable, while small changes at the lowest light levels (1 to 2 - doubles the intensity) are particularly noticeable.

If you refer to a "sine function" then the visual characteristic is that the change from increasing to decreasing and vice versa, is relatively inobvious as the sine wave "flattens" at these points. Because changes are not obvious at the highest intensities, this will already appear smooth. On the other hand, even a genuine sine wave would be quite jerky at the lower levels.

The easiest way then to smooth it out, will be to simply set a minimum value for the light level, say 10. You still allow the actual value to ramp down to zero, but you insert an extra function:

void minwrite(int whichpin, int level) {
  if (level > 10) analogWrite(whichpin, level);
  else analogWrite(whichpin, 10);
}

Here's where I am so far:

int Input2 = A1;//Deflector Switch

int ledPin9 = 9;//PWM Output to LED1
int ledPin10 = 10;//PWM output to LED2

void setup() {
  // put your setup code here, to run once:
  pinMode (A1, INPUT_PULLUP);
}

void loop() {
  static float in = 4.712;
  float out;
 // do input, etc. here - as long as you don't pause, the LED will keep pulsing
  if (digitalRead(A1) == LOW)//Detects Deflector Switch On
  {
  in = in + 0.0001;
  if (in > 10.995)
    in = 4.712;
  out = sin(in) * 127.5 + 127.5;
  analogWrite(ledPin9,out);
  }
}

This provides a nice even fade on pin 9 which begins when input A1 is LOW. It then cycles up and down constantly until A1 is HIGH, at which point it pauses and remains steady until A1 is LOW again and the cycle resumes, repeating indefinitely.

All I hope to accomplish at this stage is to have pin 9 fade on and remain on while A1 is LOW, and reverse the cycle when A1 is HIGH. Methinks this should be really simple, but the logic has escaped me thus far.