LEDs PWM within determined range of time - FOR cycle without FOR/WHILE

Hello,

I am trying to light up some LEDs using PWM to create a dimming effect from zero to full power and back within a specific range of time. These ranges of time are the classic ones of a camera and, to have them displayed correctly on a screen I have created a struct called exposure so to have both the numerical values in millisec and the corresponding text.

struct exposure{
  long arduinoexp;
  String displayexp;}
}
exposure[]{
  {125, "1/8'"},
  {167, "1/6'"},
  {200, "1/5'"},
  {250, "1/4'"},
  {300, "0''3"},
  {400, "0''4"},
  {500, "0''5"},
  {600, "0''6"},
  {800, "0''8"},
  {1000, "1''0"},
  {1333, "1''3"},
  {1666, "1''6"},
  {2000, "2''0"},
  {2500, "2''5"},
  {3200, "3''2"},
  {4000, "4''0"},
  {5000, "5''0"},
  {6000, "6''0"},
  {8000, "8''0"},
  {10000, "10''0"},
  {13000, "13''0"},
  {15000, "15''0"},
  {20000, "20''0"},
  {25000, "25''0"},
  {30000, "30''0"},
};

I set the desidered exposure value with a rotary encoder and I created a specific counter (which in my code is counter 0 , associated with rotary encoder 0 , so to have the possibility to add other counters to other rotary encoders, may I have the necessity).

Then I created a function to recall when I need to switch on the LEDs with two for cycle from 0 to 255 and from 255 to 0 each time with a delay that varies accordingly to the selected exposure value.

void ledlights_regular(){
  initial_time = millis();
  for (int i = 0; i < 255; i++) {
    analogWrite(Channel_1, i);
    delay((float)exposure[counter[0]].arduinoexp/510.0);
  }
  for (int i = 255; i >= 0; i--) {
    analogWrite(Channel_1, i);
    delay((float)exposure[counter[0]].arduinoexp/510.0);
    }
  Serial.print("Duration: "); Serial.println(millis()-initial_time);  
  delay(500);
}

It seems to work fine with long exposure values:
Here are the test I ran (millisec values taken from the serial):
30 Secs --> Duration 29666 millisec
25 Secs --> Duration 25065 millisec
20 Secs --> Duration 19956 millisec

Then things start getting worse:

6 Secs --> Duration 5649 millisec
5 Secs --> Duration 4627 millisec
4 Secs --> Duration 3606 millisec
3.2 Secs --> Duration 3094 millisec

The problem arise when I lower the exposure values, it seems to be like some "rounding" occurs with certain blocks of values:

2 Secs --> Duration 1561 millisec
1.6 Secs --> Duration 1560 millisec
1.3 Secs --> Duration 1051 millisec

1 Sec --> Duration 541 millisec
0.8 Secs --> Duration 540 millisec
0.6 Sec --> Duration 541

0.5 Secs --> Duration 27 millisec
0.4 Secs --> Duration 27 millisec
0.3 Secs --> Duration 27 millisec
1/4 Secs --> Duration 26 millisec
1/5 Secs --> Duration 25 millisec
1/6 Secs --> Duration 25 millisec
1/8 Secs --> Duration 27 millisec

I've thought it must been something connected to the rounding which takes place here:

delay((float)exposure[counter[0]].arduinoexp/510.0);

So I tried to, instead of making the calculation each time in "delay", to create a float variable which would incorporate ther result prior to enter the for cycle like:

void ledlights_regular(){
  float led_interval = (float)exposure[counter[0]].arduinoexp/510.0);
  initial_time = millis();
  for (int i = 0; i < 255; i++) {
    analogWrite(Channel_1, i);
    delay(led_interval);
  }
  for (int i = 255; i >= 0; i--) {
    analogWrite(Channel_1, i);
    delay(led_interval);
    }
  Serial.print("Duration: "); Serial.println(millis()-initial_time);  
  delay(500);
}

This would compile and upload but then arduino seems to freeze, first time it happens in my life...

If anyone has any suggestions on how to solve this it would be of great help!

Many many thanks,

Alessandro

Use unsigned long for millis, no floating point variables and arithmetic.
Also avoid delay() for proper timing, use millis() instead.

When the delay is 0 milliseconds, then you don't need a delay. The 512 calls to analogWrite() and delay(0) take 27 milliseconds. That is normal.
When the delay is 1 millisecond, and it is executed 512 times, then the total duration is half a second.

When the time is around 1 second, the delay is 541ms, because delay(1) is executed 512 times.
When the time is around 0.3 seconds, the delay is 27ms, because delay(0) and analogWrite() are executed 512 times.

Thanks for the hint!

I tried this, just to make it simple to begin with it just switch on and off without PWM.
Still, there must be an error as it doesn't work...
It lights up and never turn off...

void ledlights_static(){
  period = exposure[counter[0]].arduinoexp;
  start_time = millis();
  digitalWrite(Channel_1, HIGH);
  if(millis() >= start_time + period){
      digitalWrite(Channel_1, !digitalRead(Channel_1));
  }
  delay(500);
}

Sorry, I feel dumb, I've tried 45 minutes many different version of it but I cannot make it work...

Sorry,

I haven't seen the second reply.
I see what you mean, so I assume I would need to use delayMicroseconds instead of delay or micros() instead of millis().

I tried this:

void ledlights_regular(){
  initial_time = millis();
  period = ((float)exposure[counter[0]].arduinoexp/510.0)*1000;
  Serial.print("Period: "); Serial.println(period);
  for (int i = 0; i < 255; i++) {
    analogWrite(Channel_1, i);
    delayMicroseconds(period);
  }
  for (int i = 255; i >= 0; i--) {
    analogWrite(Channel_1, i);
    delayMicroseconds(period);
    }
  Serial.print("Duration: "); Serial.println(millis()-initial_time);  
  delay(500);
}

It works really well for what I need to do.
It is still not 100% perfect but fine for what I am trying to achieve! THANKS!

By the way, I am thinking about it, but how do I achieve a for cycle pausing with micros() (or millis())?
I am thinking about it but I cannot see the solution...

MANY THANKS TO YOU ALL!

See BlinkWithoutDelay example for proper timing.

Yes,

I've looked at it but I don't find it very intuitive to transform it into a FOR cycle... :confused:

Thanks anyway for the hint.

Also don't use FOR or WHILE loops, that's what loop() is doing already. Learn to do several things at the same time instead of only a single sequential control flow. Intuition from the analog world is not the way to go in digital land.

Thank you!

I've read the documentation about delayMicroseconds and I've came up with this solution.
I don't know if it would be an engineer approved solution but it works for me and I hope it will help others with the same problem!

void ledlights_regular(){
  Serial.println("Leds Lights Regular");
  initial_time = millis();
  period_micros = ((float)exposure[counter[0]].arduinoexp/510.0)*1000;
  period_millis = ((float)exposure[counter[0]].arduinoexp/510.0);
  
  if(period_micros < 16383){
    for (int i = 0; i < 255; i++) {
      analogWrite(Channel_1, i);
      delayMicroseconds(period_micros);
      }
    for (int i = 255; i >= 0; i--) {
      analogWrite(Channel_1, i);
      delayMicroseconds(period_micros);
      }
  
  }
  
  if(period_micros >= 16383){
    for (int i = 0; i < 255; i++) {
      analogWrite(Channel_1, i);
      delay(period_millis);
    }
    for (int i = 255; i >= 0; i--) {
      analogWrite(Channel_1, i);
      delay(period_millis);
      } 
  }
  
  Serial.print("Duration: "); Serial.println(millis()-initial_time);  
  delay(300);
}

Let me know what you think!
Thanks again!

Alessandro

Your code is a very bad example how things should not be done.

Can you give me a more helpful comment explaining the reason why it is a bad example?

See my preceding posts, read and understand.

Okay,

I truly tried my best here.
I have read what you wrote about not using FOR and WHILE cycle but I cannot find of any other way to accomplish this without.
To be precise, what is killing me is the fact that I am writing a function that has to occur when I push a button and it has to complete its cycle of dimming light even if the button has been released.
Without a FOR loop I cannot see any other way to encapsulate it into the mail void loop() and still make it go through the whole dimming cycle it has to do (as the conditions, read the button, won't be longer verified).

I'll keep on trying but I hope to be on the good road with this code.

void ledlights_regular(){
  Serial.println("Leds Lights Regular");
  initial_time = millis();
  unsigned long currentMicros = micros();
  unsigned long previousMicros = 0;
  period_micros = ((float)exposure[counter[0]].arduinoexp/508.0)*1000;

  for (int i = 0; i < 255; i++){   
      while (currentMicros - previousMicros <= period_micros){currentMicros = micros();}
      previousMicros = currentMicros;
      analogWrite(Channel_1, i);
    }
  for (int i = 255; i >= 0; i--){
      while (currentMicros - previousMicros <= period_micros){currentMicros = micros();}
      previousMicros = currentMicros;
      analogWrite(Channel_1, i);
    }
    
  Serial.print("Duration: "); Serial.println(millis()-initial_time);  
  delay(300);
  }

See the link in #7. Do not only read but also practice the examples.

You also may be interested in finite automaton/state machine coding and Task Macros.

I've spent the whole afternoon figuring out a way to accomplish this...

I have seen all possible links and I'm here with three books in front of me to read all I possibly could.

I came up with this, it COMPLETELY avoids any delay(), for(), while().

bool ledlights_regular(bool triggerFunction){
  
  static bool trigger = triggerFunction;
  static int i = 0;
  static bool dimming = true;
  static unsigned long initial_time;
  unsigned long currentMicros = micros();
  static unsigned long previousMicros = 0;
  period_micros = ((float)exposure[counter[0]].arduinoexp/508.0)*1000;
  
  if (i == 0) initial_time = millis();
  if (i == 0) Serial.print("Leds Lights Regular");

  if (trigger == true && dimming == true && currentMicros - previousMicros >= period_micros){
    previousMicros = currentMicros;
    i++;
    //Serial.println(i);
    analogWrite(Channel_1, i);
    if (i == 255) dimming = false;
  }
  if (trigger == true && dimming == false && currentMicros - previousMicros >= period_micros && i >= 1){
    previousMicros = currentMicros;
    i--;
    //Serial.println(i);
    analogWrite(Channel_1, i);
  }

  /*if (i == 0){
    trigger = false;
    dimming = true;
    Serial.print("Duration: "); Serial.println(millis()-initial_time);
    return trigger;
  }*/
  if (i == 0) trigger = false;
  if (i == 0) dimming = true;
  if (trigger == false) Serial.print("Duration: "); Serial.println(millis()-initial_time);
  return trigger;
  } 

void loop(){
if (button == true || triggerFunction == true){
    triggerFunction = true;
    triggerFunction = ledlights_regular(triggerFunction);
  }

I'm sure there are many mistakes as "it works" but it has some problems that I truly can't figure out...

  1. It would run only once, then I'll have to reset arduino.
    I sure it is something connected with trigger -- triggerFunction but I've spent 1h figuring out what it could be and I can't...
  2. IFs are driving me crazy...
    I don't get why if I write single lines (repeating the condition) they work, if I write a block it doesn't.
    I left it as a comment in the code for you to see...
    Also, they do not work properly as this part:
  if (i == 0) trigger = false;
  if (i == 0) dimming = true;
  if (trigger == false) Serial.print("Duration: "); Serial.println(millis()-initial_time);
  return trigger;

Keeps on returning values even while the led is still dimming (therefore i cannot be == 0).
Driving me crazy...
If you could help me it would be very appreciated.
Many thanks,

Alessandro

Multiple statements after IF must be enclosed in curly brackets, else all but the first statement execute unconditionally.

Without comments in your code it's hard to say what you want and what you did wrong. Very many variables of unknown purpose increase the confusion and your problems.

Yes,

Sorry, I've seen just now the very last statement, I've corrected it!

I repost the code here with all the comments, I hope it will be clearer to understand.

Now apparently the issue is that this would run only once.
I guess I am doing something wrong with trigger and triggerFunction.

Let me know your thoughts,

Many thanks!

void loop(){
/* In the loop there are more things but this is the one that is creating problem.
The aim of this part is to create a FOR cycle without a FOR statement.
To do so, as the function must be triggered by a button I've created an IF cycle which applies both
if the button is pressed AND/OR if the variable "triggerFunction" is TRUE.
The idea behind it is to trigger the function, for the first time, with the button and then,
again and again, with triggerFunction == TRUE, which will get the value FALSE, only when certain conditions
in ledlights_regular() (the triggered function) will be met.
ledlights_regular(); return a bool variable (trigger) which is copied to triggerFunction so to turn it FALSE.*/

if (button == true || triggerFunction == true){
    triggerFunction = true;
    triggerFunction = ledlights_regular(triggerFunction);
  } 

}

bool ledlights_regular(bool triggerFunction){
  
  static bool trigger = triggerFunction; // copies the value to triggerFunction to trigger
  static int i = 0; // this is the parameter used to increase the LEDs with PWM
  static bool dimming = true; // this define if the brightness must be increased or decreased
  static unsigned long initial_time; // this is to check how much time does it take to complete the whole cycle
  unsigned long currentMicros = micros(); // to define the delay (as in blinkWithoutDelay)
  static unsigned long previousMicros = 0; // to define the delay (as in blinkWithoutDelay)
  period_micros = ((float)exposure[counter[0]].arduinoexp/508.0)*1000; // to define the overall time for the function
  
  if (i == 0) initial_time = millis(); // This because I need to set the initial time only once, not at all repetition of the function
  if (i == 0) Serial.print("Leds Lights Regular"); // Same as before

  if (trigger == true && dimming == true && currentMicros - previousMicros >= period_micros){
    // This increase the PWM by 1 every time
    previousMicros = currentMicros;
    i++;
    //Serial.println(i);
    analogWrite(Channel_1, i);
    if (i == 255) dimming = false; // It set dimming to false so to avoid the first IF statement and go to the next
  }
  if (trigger == true && dimming == false && currentMicros - previousMicros >= period_micros && i >= 1){
    // This decrease the PWM by 1 every time
    previousMicros = currentMicros;
    i--;
    //Serial.println(i);
    analogWrite(Channel_1, i);
  }
  // This block in curly brackets doesn't work for some reasons...
  /*if (i == 0){
    trigger = false;
    dimming = true;
    Serial.print("Duration: "); Serial.println(millis()-initial_time);
    return trigger;
  }*/ 
  
  // If I put singular statements it works...mistery...
  if (i == 0) trigger = false; // i = 0 only once it has gone up to 255 and back to 0, so end of the cycle.
  if (i == 0) dimming = true;
  if (trigger == false) {Serial.print("Duration: "); Serial.println(millis()-initial_time);}
  return trigger; // it return the value trigger which will be copied to triggerFunction in the main void loop() to stop the cycle
  }

This way you call ledlights_regular(true). Is that what you want? Remove the parameter from ledlights_regular() as a first simplification of your code.

You also can call ledlights_regular(button) always in loop() and let the subroutine manage its on/off state.

I might have been unprecised in my description: I need the PWM cycle to continue even if the button is not pressed any longer within the dimming time.
In other words, I press the button once, but the function need to keep on running for the selected amount of time, which could be up to 30 seconds.
That is the reason why I thought of creating a trigger which would be copied to the main loop to trigger the function again and again till it has finished its cycle.

Is there a way to repeat the function, without a FOR cycle, without "going back" to void loop() with a variable?

Many thanks!

Maybe could you also take a look at this?
I feel like it's somehow connected to the same issue of having multiple functions running at the same time.
The i2c display function is blocking me but I've tried my best to do something as explained in BlinkWithoutDelay.
THANK YOU FOR YOUR PRECIOUS HELP!

You already have the i, dimming and trigger variables which tell the current state. Thus the subroutine can do everything (up/down/stop) itself. Only the start signal from the button has to be passed in from outside as a parameter.