How to Keep Fade and Blink without Delays sequential?

Hi Guys

Can someone give me a BIG hint on how to solve what seems as first to be a simple problem in logic?

I want to be able to write a function that, when called, sequentially (1) fades an led up from 0 to 50 in 500ms, then (2) turns the led full on for 100ms, then (3) fades the led down from 255 to 0 in 200ms. All this will occur as a single continuous sequence when called from within the final sketch.

Because other things are planned to happen at the same time, delays are out of the question and I will be using millis for the timings.

I am thinking to use a while loop with analogWrite for part (1) (does this sound like the most sensible way to limit the fade up to a set value?), a digitalWrite for part (2) and then analogWrite again for part (3), [u]but[/u] I can't for the life of me work out how to combine the three tasks in such a way that they are sequential rather than simultaneous :slightly_frowning_face: .

Before anyone asks, I don't have any code yet as I've got myself fixated on this one problem and haven't commited anything to IDE yet - but I've got loads of scrap paper though! The remainder of the sketch will simply comprise of several leds blinking without delay in a random manner and a button press detection routine which will be used to trigger the problem function.

I hope to run the final sketch (if it ever sees the light of day :-\ ) on an ATtiny85.

I would guess that this is probably just a question of the way the routine is nested, and the use of the timing variables, but I have been struggling with this problem for over a week now and its getting to the stage where I feel like giving up. Help :) !

Bernie

analogWrite(255) has the same effect as digitalWrite(HIGH). If you open up the code inside analogWrite() you will see that it uses this as a shortcut.

If you want the Arduino to do other stuff at the same time as the LED is changing brightness then don't use a while(). You should call a function from your main loop, thousands of times per second. That function has its own variables, declared as static, which keep track of the time it was started and it can do the analogWrite().

Thanks MorganS :)

Sorry for my stupidity, but even though, thanks to MorganS, I now see the importance of static variables for the timing and maybe brightness level of the controlled led, I still cannot work out how to move from one step of the routine to another within the function.

Ie, when the initial ramp up to 100 has been achieved, which I assume I test for by an IF statement on the brightness level, how on earth do I then proceed to the next step (full brightness for a set period) and then from that to the final ramp down?

Do I need three seperate sets of variables, one set for each step? I don't know if I should be testing for progress through the routine by querying the time variable or the brightness variable - or even, both.

Its not a block of code that I need written for me, that way I learn nothing, but a suggestion for a basic logic tree that I can attempt to code around.

Like I say, apologies for my lack of understanding and knowledge, but I've been wrangling this question for so long I can no longer see the wood for the trees!

Bernie

Normally you'd have a state variable, recording which phase of the sequence you are in, and dispatch using switch/case at top level to the correct code for handling that phase.

Some phases are just waiting for a timeout, others are waiting for a timeout and stepping the brightness, testing for reach the end condition for that phase. When the code handling a phase of the sequence determines that phase is over, it simply updates the state variable to the number of the next phase...

Within each phase there may be other state variables, such as current brightness and timeout times. Often these can be shared between phases since only one phase is active at once.

Learn more about "state machines" and "dry running" and things should make more sense.

Thanks MarkT, I'll get to research state machines as soon as I can. That bit I can do!

Do I assume by "dry running" you mean testing each code fragment in isolation before adding on the next fragment?

Bernie

The demo several things at a time shows how to use millis() (rather than the delay() function) to manage time .

...R

This doesn’t need a state machine. The time is the state. You just need a way of defining what happens at each time.

The code below sets up an array of times (milliseconds.) At time zero you want one brightness, at time 500 you want a different brightness and in between you want a straight-line interpolation. For “full brightness” for a time period, just make two adjacent times brightness 255.

If you want something other than a straight line (the perceived brightness is non-linear) then just use a lot more times and brightnesses in the arrays to get the effect you want.

/*Example programmable fade
  Needs an LED on a PWM pin
  (The built-in LED on an Uno is not PWM but it is on most other Arduinos)
*/
#define LED_PIN 13

#define NUM_STEPS               4                  //Number of entries in the two arrays below
unsigned long int TimeLine[] = {0, 500, 600, 800}; //Time (in milliseconds) for each event on the timeline - first item should be zero, then increase from there
byte Brightnesses[] =          {0, 255, 255, 0  }; //LED analogWrite() value for each of the times above


void doLED(boolean Start = false) {
  //adjust the brightness of the LED based on the timeline 
  static unsigned long int LED_Start = 0; //keep this as static so we can remember when we originally started
  unsigned long TimeDelta;
  int i;
  byte OutputVal;
  if(Start) {
    //new start - save the current millis as the original start time
    LED_Start = millis();
    pinMode(LED_PIN, OUTPUT);
  }
  TimeDelta = (millis() - LED_Start) % TimeLine[NUM_STEPS-1]; //This finds our position in the timeline
                                                              //modulo operator (%) means we only get values
                                                              //between zero and the maximum
  //now find the timeline entries on each side of the current position
  for(i=1; i<NUM_STEPS; i++) {
    if(TimeLine[i] > TimeDelta) break;  //leave the loop once we find a match
  }
  //i is now pointing to the first entry greater than the current time
  //now linearly scale between that entry and the previous
  OutputVal = map(TimeDelta, TimeLine[i-1],     TimeLine[i], 
                             Brightnesses[i-1], Brightnesses[i]);

  //now light the LED!
  analogWrite(LED_PIN, OutputVal);
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.println("Starting!");
  doLED(true); //get the LED started at the current time
}

void loop() {
  // put your main code here, to run repeatedly:
  const unsigned long WriteTimer = 1000;
  static boolean Written = false;
  
  doLED(); //call this at the top of every loop and inside any time-consuming processes so the LED can be updated very often
  
  //The following code is the simplest I can think of to do something only once per second, without using delay
  if((millis() % WriteTimer) == 0 && !Written) {
    Written = true;
    Serial.println("Working!");
  }
  if((millis() % WriteTimer) != 0 && Written) {
    Written = false;
  }  
}

Once again, its a big Thank You MorganS :) .

Your explanation and demo sketch has given me hope at last. Your sketch performs the task perfectly. So, if I have grasped the principle properly, if I were to plot a graph of the led's brightness against time, the two arrays would hold the x (time) and y (brightness) values of that graph.

The sketch effectively traverses the time (x) line until it reaches the next value held in the x array and looks up the corresponding value held in the brightness (y) array, it then extrapolates the brightness slope between that value and the next and then applies it to the led over this time interval, then moves on the the next time interval and so on. Does that sound ok?

There's obviously a lot more detail going on in there which I need to get my head around, but at least I now have a principle that works and a sketch structure to study.

Many Thanks and kudos (and karma as well), again! Bernie

Yes, that's exactly what it's doing. The key is to keep your main loop short enough to be able to call this function at least 20 times per second, ideally 1000 times per second. If you have a long delay - for example waiting for the user to press a button - then you need to be able to call the function during that delay.

Actually, I think the sketch will be quite short, as I plan on the potential sketch comprising just a few routine calls (a couple of leds flashing at different rates (without delay), a button press function with de-bouncing which will call the whole ramp up, constant on ramp down function to perform just a single cycle per button press).

I have all the individual functions working in isolation as individual test sketches, its now time to attempt to combine them all into one sketch - that’s where my next head banging session starts! I could be back, to paraphrase Arnie 8)

Bernie

Sorry to resurrect this thread people, but as warned, I do have another question, this time regarding the triggering of a function on demand.

I've brought this thread back rather than start a new one, as the function in question is basically the function that MorganS kindly came up with in reply #6 above. That function, to fade an led whilst flashing others without delay works perfectly so I haven't messed with it except to change the timings. The problem I have set myself is how to trigger that function, as written, to perform just one cycle in response to a button press.

The actual button press and debounce routine associated with it is not a problem, but working out how to use a change of flag, or a choice of case or whatever to run the fade routine just once and then wait for another button press is proving more difficult than my limited knowledge can cope with! I think my main problem is that I understand the principle of the function, but I don't follow the programming logic well enough to work out how to have the function act on demand under the command of a button press.

Can anyone (MorganS?) point me in the right direction? What do I need to pass to the function in order for it to know when to stop and return?

TIA

Bernie

STDummy: The problem I have set myself is how to trigger that function, as written, to perform just one cycle in response to a button press.

Post your latest code so we have something to start from.

...R

Other than MorganS's basic premis, I haven't actually written my sketch yet Robin2, as I couldn't work out how to call the function used in the example that MorganS posted for just a single cycle, but I'll produce some code with the function I want to call as soon as I get home today. It won't be pretty, but I hope it'll demonstrate what I want to do.

Cheers Bernie

There's two ways to do this:

  1. When you want the LED to stop fading, just stop calling doLED()
  2. Create some other parameter or global variable which doLED() will use to decide when to stop.

So long as you're not planning to extend this to multiple LEDs or multiple different fade patterns, then option 1 may be the best. Obviously the LED will 'stick' in one brightness when doLED() is not called, so you probably want to digitalWrite(LED_PIN, LOW) to turn it off after you last call doLED();

Thanks again MorganS :slight_smile:

I had been thinking roughly along those lines, but I don’t seem to be able to use the correct parameter for stopping doLED().

The code I have rushed together so far is this:

// --------CONSTANTS---------------
const int Led[] = { 5, 6 }; // pin numbers for fader leds
const int buttonPin = 0;    // the pin number for the button
const unsigned long debounceTime = 10;  // milliseconds
unsigned long switchPressTime;  // when the switch last changed state

#define NUM_STEPS               4                  //Number of entries in the two arrays below
unsigned long int TimeLine[] = {0, 500, 600, 800}; //Time (in milliseconds) for each event on the timeline - first item should be zero, then increase from there
byte Brightnesses[] =          {0, 50, 255, 0  }; //LED analogWrite() value for each of the times above


//------------Variables---------------------

byte oldSwitchState = HIGH;  // assume switch open because of pull-up resistor
int ledSide = 0;  // which fader led to fade
int flag = 0; // fader trigger
int i;
//------------Fade Function (butchered from MorganS example)---------------------

void doLED(boolean Start = false) {

  //adjust the brightness of the LED based on the timeline
  static unsigned long int LED_Start = 0; //keep this as static so we can remember when we originally started
  unsigned long TimeDelta;
  //int i;
  byte OutputVal;
  if (Start) {
    //new start - save the current millis as the original start time
    LED_Start = millis();
    pinMode(Led[0], OUTPUT);
    pinMode(Led[1], OUTPUT);
  }
  TimeDelta = (millis() - LED_Start) % TimeLine[NUM_STEPS - 1]; //This finds our position in the timeline
  //modulo operator (%) means we only get values
  //between zero and the maximum
  //now find the timeline entries on each side of the current position
  for (i = 1; i < NUM_STEPS; i++) {
    if (TimeLine[i] > TimeDelta) break; //leave the loop once we find a match
  }
  //i is now pointing to the first entry greater than the current time
  //now linearly scale between that entry and the previous
  OutputVal = map(TimeDelta, TimeLine[i - 1],     TimeLine[i],
                  Brightnesses[i - 1], Brightnesses[i]);

  //now light the LED!
  analogWrite(Led[ledSide], OutputVal);
}


//========================================

void setup() {

  Serial.begin(9600); // for debugging

  // set the button pin as input with a pullup resistor to ensure it defaults to HIGH
  pinMode(buttonPin, INPUT_PULLUP);
  unsigned long currentTime = millis();
  unsigned long loopTime = currentTime;
  doLED(true);
}

//========================================

void loop() {

  pushButton();
  if (flag == 1) {
    doLED();
    //Serial.println (TimeLine[i]);
    if (TimeLine[i] == 800) {
      digitalWrite(Led[ledSide], LOW);
      flag = 0;
    }
  }
}

//------------Button Press function----------

void pushButton ()
{
  // see if switch is open or closed
  byte switchState = digitalRead (buttonPin);

  // has it changed since last time?
  if (switchState != oldSwitchState)
  {
    // debounce
    if (millis () - switchPressTime >= debounceTime)
    {
      switchPressTime = millis ();  // when we closed the switch -
      oldSwitchState =  switchState;  // remember for next time through
      if (switchState == LOW) {
        //Serial.println ("routine called");
        flag = 1;//doLED (); // *******THIS IS WHERE THE FUNCTION NEEDS TO BE TRIGGERED TO CYCLE JUST ONCE****
        ledSide ++;
        if (ledSide == 2)
          ledSide = 0;
      }
    }
  }
}

This sort of works, but each button press (which alternates the function from one led to another at each button press) sometimes produces a complete cycle and sometimes does not. I am fairly certain that using the TimeLine variable is totally the wrong parameter to use, but other than keeping track of the total length of time taken for one cycle in a separate variable and then using that (ie when cycletime is equal to 1900mS) to stop doLED I’m stumped. I have tried using that method by the way but however I wrote it, it didn’t have any effect :frowning: - but is that the approach I can use do you think?

I’m sure I’m missing something that is probably glaringly obvious to others, but I just can’t see it. Can someone drop me a bigger hint? please?

Thanks for all the help this far :slight_smile:
Bernie