Change code to add non-blocking delay and control brightness and speed with pot - most work is done

Hello all :slight_smile:
I'm not a coder, so I've been piecing and modding from what I could find. Everything's working as I'd like, except I need to:

  • In Case 4: Control the overall brightness of all LED's with pot 1. Also, create individual delays without blocking.
  • In Case 5: Control the overall brightness of all LED's with pot 1. Also, control fade speed with pot #2.
  • In both cases 4 and 5, right now, each LED finishes fading out before the next one starts fading in. I want each LED to start fading in exactly when the last one starts fading out.

I'm using an Arduino Pro Mini and here is the code:

const int buttonOne = 2;  
const int potPin1 = A1; 
const int potPin2 = A2;
const int motorLowerBLUE = 3; 
const int motorMiddleGREEN = 5;
const int motorUpperRED = 6;

int buttonOneState;
int lastButtonOneState = 1;  
unsigned long lastDebounceMillis = 0;
const unsigned long debounceDelay = 30;

int potValue1 = 0;
int potValue2 = 0;
int mode;
int prevMode = 0;
int ledState = LOW;
unsigned long previousMillis = 0;

void setup() {
  Serial.begin(9600);  
  pinMode(buttonOne, INPUT_PULLUP);
  pinMode(potPin1, INPUT);
  pinMode(potPin2, INPUT);  
  pinMode(motorUpperRED, OUTPUT);      
  pinMode(motorMiddleGREEN, OUTPUT);   
  pinMode(motorLowerBLUE, OUTPUT);   
  mode = 0;
  reset();
}

void loop() {
  // always check on the pot values
  potValue1 = analogRead(potPin1);
  potValue2 = analogRead(potPin2);
  
  // Check pushbutton?
  int currentMode = checkModeButton(prevMode);
  
 
  // ***** add or remove modes - also added in "mode functions" section ******
  switch (currentMode) {
    
    case 0:  // all off
      break;
      
    case 1:  // Bue LED, brightness controlled by pot 1
      blueDimmer(potValue1);
      break;

    case 2:  // Green LED, brightness controlled by pot 1
      greenDimmer(potValue1);
      break;

    case 3:  // Red LED, brightness controlled by pot 1
      redDimmer(potValue1);
      break;
      
    case 4:  // "stop light" effect, overall brightness controlled by pot 1
      stopLight(potValue1);
      break;
      
    case 5:  // "stop light" effect, overall brightness controlled by pot 1, speed by pot 2
      stopLight2(potValue1);
      break; 
      
    default:  // nothing happening
      break;
  }
  prevMode = mode;
}


// inner workings
int checkModeButton(int prevMode) {
  int buttonOneReading = digitalRead(buttonOne);
  if (buttonOneReading != lastButtonOneState) {
    lastDebounceMillis = millis();
  }
  if ((millis() - lastDebounceMillis) > debounceDelay) {
    if (buttonOneReading != buttonOneState) {
      buttonOneState = buttonOneReading;  // let's double check
      if (buttonOneState == LOW) {
        int lastMode = prevMode;
        Serial.print("\n\nlast mode: ");
        Serial.println(lastMode);
        mode++;
        Serial.print("button pressed: ");
        Serial.print("current mode = ");
        Serial.println(mode);
        Serial.println();     

// ******** change nuber of modes in "(mode >= x)" below when adding or removing modes *****************
      }
      if (mode >= 6) mode = 0;
      else if (mode == 0) Serial.println("\nreset to mode 0");
      reset();
    }
  }
  lastButtonOneState = buttonOneReading;
  return mode;
}


// ****************** mode functions - also add in "add or remove" mode section  ********************

// Case 1 - Steady blue-only dimmer mode - brightness controlled by pot 1

void blueDimmer(int potValue1) {
  int analogMotor = potValue1 / 4;  /* Changing this can change the nuber of resets that happen 
                                       when turning brightness pot. Original text that was here: 
                                       "Store potValue1 / 4 to match 10-bit ADC to 8-bit DAC for LEDs"*/
  analogWrite(motorLowerBLUE, analogMotor);
  analogWrite(motorMiddleGREEN, LOW);
  analogWrite(motorUpperRED, LOW);
}


// Case 2 - Steady green-only dimmer mode - brightness controlled by pot 1
void greenDimmer(int potValue1) {
  int analogMotor = potValue1 / 4;  /* Changing this can change the nuber of resets that happen 
                                       when turning brightness pot. Original text that was here: 
                                       "Store potValue1 / 4 to match 10-bit ADC to 8-bit DAC for LEDs"*/
 
  analogWrite(motorLowerBLUE, LOW);
  analogWrite(motorMiddleGREEN, analogMotor);
  analogWrite(motorUpperRED, LOW);
}

// Case 3 - Steady red-only dimmer mode - brightness controlled by pot 1

void redDimmer(int potValue1) {
  int analogMotor = potValue1 / 4;  /* Changing this can change the nuber of resets that happen 
                                       when turning brightness pot. Original text that was here: 
                                       "Store potValue1 / 4 to match 10-bit ADC to 8-bit DAC for LEDs"*/
  analogWrite(motorLowerBLUE, LOW);
  analogWrite(motorMiddleGREEN, LOW);
  analogWrite(motorUpperRED, analogMotor);
}

// Case 4 - Fading "stop light" mode: blue-green-red-green-repeat, brightness controlled by pot 1

// ****** In Case 4, I need to:
//        - create individual delays, but in a 
//          non-blocking way, maybe somehow use millis?
//        - limit overall brightness with pot 1.
//        - Right now, each LED finishes fading out
//          before the next one starts fading in. 
//          I want each LED to start fading in
//          exactly when the last one starts 
//          fading out. 

void stopLight(int potValue1) {
  Serial.print("fading...");
  //int analogMotor = potValue / 4;  // Store potValue / 4 to match 10-bit ADC to 8-bit DAC for LEDs

 // blue fades in
  for (int brightness = 0; brightness <= 255; brightness++) {
    analogWrite(motorLowerBLUE, brightness);
    delay(5);
  }
  // blue fades out   
  for (int brightness = 255; brightness >= 0; brightness--) {
    analogWrite(motorLowerBLUE, brightness);
    delay(2);
  }
   // green fades in 
  for (int brightness = 0; brightness <= 255; brightness++) {
    analogWrite(motorMiddleGREEN, brightness);
    delay(5);
  }
   // green fades out
  for (int brightness = 255; brightness >= 0; brightness--) {
    analogWrite(motorMiddleGREEN, brightness);
    delay(1);
  }
  // red fades in
  for (int brightness = 0; brightness <= 255; brightness++) {
    analogWrite(motorUpperRED, brightness);
    delay(5);
  }
  // red fades out
  for (int brightness = 255; brightness >= 0; brightness--) {
    analogWrite(motorUpperRED, brightness);
    delay(1);
  }  
  // green fades in
  for (int brightness = 0; brightness <= 255; brightness++) {
    analogWrite(motorMiddleGREEN, brightness);
    delay(5);
  }  
 // green fades out
  for (int brightness = 255; brightness >= 0; brightness--) {
    analogWrite(motorMiddleGREEN, brightness);
    delay(1);
  } 
} 

// Case 5 - Fading "stop light" mode: blue-green-red-green-repeat - brightness by pot 1, speed by pot 2


// ****** In Case 5, I need to:
//        - Create same exact delays between each LED, without
//          code bocking by "delay" command. 
//          Pot 2 will determine those delays. Turn pot 2
//          up, LED's change fast. Turn pot 2 down, 
//          LED's change slow.
//        - limit overall brightness with pot 1.
//        - Right now, each LED finishes fading out
//          before the next one starts fading in. 
//          I want each LED to start fading in
//          exactly when the last one starts 
//          fading out. 


void stopLight2(int potValue2) {
  Serial.print("fading 2...");

 // blue fades in
  for (int brightness = 0; brightness <= 255; brightness++) {
    analogWrite(motorLowerBLUE, brightness);
    //********** delay here - time determined by pot 2
  }
  // blue fades out   
  for (int brightness = 255; brightness >= 0; brightness--) {
    analogWrite(motorLowerBLUE, brightness);
    //********** delay here - time determined by pot 2
  }  
   // green fades in
  for (int brightness = 0; brightness <= 255; brightness++) {
    analogWrite(motorMiddleGREEN, brightness);
    //********** delay here - time determined by pot 2
  }
   // green fades out
  for (int brightness = 255; brightness >= 0; brightness--) {
    analogWrite(motorMiddleGREEN, brightness);
    //********** delay here - time determined by pot 2
  }
  // red fades in
  for (int brightness = 0; brightness <= 255; brightness++) {
    analogWrite(motorUpperRED, brightness);
    //********** delay here - time determined by pot 2
  }
  // red fades out
  for (int brightness = 255; brightness >= 0; brightness--) {
    //********** delay here - time determined by pot 2
  }
  // green fades in
  for (int brightness = 0; brightness <= 255; brightness++) {
    analogWrite(motorMiddleGREEN, brightness);
    //********** delay here - time determined by pot 2
  }   
 // green fades out
  for (int brightness = 255; brightness >= 0; brightness--) {
    analogWrite(motorMiddleGREEN, brightness);
    //********** delay here - time determined by pot 2
  } 
}

// reset mode - all is off
void reset() {
  digitalWrite(motorUpperRED, LOW);
  digitalWrite(motorMiddleGREEN, LOW);
  digitalWrite(motorLowerBLUE, LOW);
}

Hope You’re an artist and can draw a schematics, and post it.

Then you need a new case that combines the functions.

1 Like

Not a schematic-artist, but I posted one below.

Thank you Paul. Being a non-coder, I'll have to look that up, but I would really appreciate tips on how to do that.

To learn about "non-blocking delays" (which are not delays), study this excellent tutorial:

https://www.baldengineer.com/blink-without-delay-explained.html

Thank you. I appreciate the article and I tried to read it, but it expects that I already know enough of the language of a coder. I'm too new at this. I will continue to learn, just not fast enough for this project.

Unfortunately, to be successful in this hobby requires that you learn the basics of the C/C++ coding language.

Contrary to the title of your post, "most work" is not done.

1 Like

To that end: https://docs.arduino.cc/language-reference

Your Delay Between Colors Issue

Well, your slight delay between the end of one color and the start of the next is you ramp down to duty-cycle == 0 then delay(x) and then start the next color ramp up with the duty-cycle == '0' and again delay(y). So between the last color duty-cycle == 1 and the current color duty-cycle of 1 you have x + y ms of delay when no color is shown. If you want to eliminate the delay, just use brightness > 0 for the ramp down loop and iterate the next from brightness = 1.

Your non-blocking delay (new approach needed)

As for your non-blocking delay (e.g. not using delay(x)), you really need to refactor your code to take the individual colors out of the for (...) loops simply time your call to change the level of a color similar to how you use millis() to measure the debounce time. So you are only calling the analogWrite() function once per-brightness and incrementing the brightness when calls to millis() between start and now reach the time you map() in ms from pot2.

That will get rid of a lot of inefficiency that will be created if you try and do it in your existing for (...) loop layout shown. (essentially you will be calling, e.g. analogWrite(motorLowerBLUE, brightness);, in a busy loop within each for() loop iteration however many times it takes to fill up the number of ms in delay you want -- busy work.

That said, you can do the busy-loop approach simply by taking the increment of brightness out of the for() loop declaration and only update brightness (and reset your iteration start milliseconds) only when you have looped for the desired amount of time. That's not really non-blocking, you have just exchanged delay() with a busy-loop that will still be looping the for() loop for the same amount of time.

I questioned whether to provide this or not, but decided to provide it to show you how you can fill in the /****** delay here with timed comparisons -- with the understanding you should move it all out of the for() loop. So here goes.

Not recommended, but your for() loop with timed (busy-looping) iterations could be done similar to:

  /* map the value from pot2 to a number of milliseconds you want, see the
   * map() function or you can interpolate between the range of ms and the raw
   * pot2 value, e.g. to map the 0-1023 raw value to 0-8 ms, do
   * (pot2 + 1) >> 7, which is just (pot2 + 1) / 128 but may optimize better.
   */
  int delayms = (pot2 + 1) >> 7;
 
  /* NOTE: start at 1, not 0 for no delay, move brightness++ into loop,
   * blue fades in, save value_iteration_start using millis(), only increment
   * brightness when: now - value_iteration_start > delayms,
   * reset value_iteration_start = now for next value iteration.
   */
  for (int brightness = 1, value_iteration_start = millis(); brightness <= 255;) {
    /* get current millis() timestamp */
    int now = millis();
    analogWrite(motorLowerBLUE, brightness);
    
    //********** delay here - time determined by pot 2
    
    /* check if brightness shown for desired number of ms, if so update
     * start time and increment brightness.
     */
    if (now - value_iteration_start > delayms) {
      value_iteration_start = now;
      brightness += 1;
    }
  }

(Note: not tested, but you get the jest of the time comparison using millis())

You are much better served by refactoring and only making the call to analogWrite() for the next value of brightness once when the time the current color and brightness has been shown reaches the number of ms the pot2 value represents. I'd rewrite or eliminate stopLight2() entirely and just handle timing how long to show each color, and what the current color is and then simply do analogWrite (motor, brightness) in the calling function. (which is basically what the links and prior comments are suggesting -- which can be implemented however you like).

Good luck!

this may be helpful

  • simplifies code eliminating redundancy, uses led array
  • everything uses pot for delay and magnitude
  • fader() gradually (delay) turns on/off LED up to magnitude
  • sequencer() calls fader() to sequence red/green/blue/green/red
#undef MyHW
#ifdef MyHW
const byte PinLeds [] = { 10, 11, 12 };
const byte PinDly  = A0;
const byte PinMag  = A1;
const byte PinBut  = A3;

#else
const byte PinLeds [] = { 3, 5, 6 };
const byte PinDly  = A2;
const byte PinMag  = A1;
const byte PinBut  =  2;
#endif

byte     butState;
const int  Nled       = sizeof (PinLeds);

unsigned long msec0;
unsigned long msecDly;
int           magMax;

enum { M_Off, M_Seq, M_Led0, M_Led1, M_Led2, M_Last };
      int  mode;

int mag;
int magDir = 1;

int ledIdx;
int ledDir = 1;

char  s [90];

// -----------------------------------------------------------------------------
void reset ()
{
    for (int n = 0; n < Nled; n++)
        analogWrite (PinLeds [n], 0);

    ledIdx = mag = 0;
}

// -----------------------------------------------------------------------------
void fader (
    int ledIdx )
{
    mag += magDir;
    if (mag <= 0)
        magDir =  1;
    else if (magMax <= mag)
        magDir = -1;

    analogWrite (PinLeds [ledIdx], mag);

    sprintf (s, "fader: led %d, mag %3d, dly %4lu, magMax %3d, mode %d",
                ledIdx, mag, msecDly, magMax, mode);
    Serial.println (s);
}

// -----------------------------------------------------------------------------
void sequencer ()
{
    if (0 == mag)  {
        ledIdx += ledDir;
        if (ledIdx <= 0)
            ledDir =  1;
        else if (Nled-1 <= ledIdx)
            ledDir = -1;
    }
    
    Serial.print ("sequencer - ");
    fader (ledIdx);
}

// -----------------------------------------------------------------------------
void loop ()
{
    msecDly = map (analogRead (PinDly), 0, 1023, 1, 100);   // 1-100 msec
    magMax  = map (analogRead (PinMag), 0, 1023, 1, 255);

    // timer
    unsigned long msec = millis ();
    if (msec - msec0 >= msecDly) {
        msec0 += msecDly;

        switch (mode) {
        case M_Off:
            reset ();
            break;

        case M_Seq:
            sequencer ();
            break;

        case M_Led0:
            fader (0);
            break;

        case M_Led1:
            fader (1);
            break;

        case M_Led2:
            fader (2);
            break;
        }
    }

    // increment mode on button press
    byte but = digitalRead (PinBut);
    if (butState != but)  {
        butState  = but;
        delay (30);         // debounce
        if (LOW == but)  {
            if (M_Last <= ++mode)
                mode = 0;
            reset ();
        }
    }
}

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

    pinMode (PinBut, INPUT_PULLUP);
    butState = digitalRead (PinBut);

    for (int n = 0; n < Nled; n++)
        pinMode (PinLeds [n], OUTPUT);

    reset ();
}

I understand. I agree that I need to continue learning. Just testing to see what kind of help is out there in the meantime. I appreciate the leads.

Great lead. Bookmarked it. Thank you.

Much appreciated. I can see that took a lot of work. I'm going to spend the next few days working with this and will let you know what comes of it.

I appreciate you taking time out for this. I'm going to study it because it does help me learn about different structures of code and how to simplify things.

When I run this code, it starts with the fade function and both pots control speed (I need to have the circuit start with no LED's on, one pot control speed and one control brightness). The other cases were about having each LED fade up, then stay solid. Right now they fade in and out.

You need to maintain two separate values (in variables, or directly using analogRead()). You read the speed pot pin and set, e.g. ledSpeed. You read the brightness pot and set, e.g. ledBrightness. Presumably the ledSpeed sets the blink rate by setting the delay value for the blink timer, and the ledBrightness is used as the PWM level in a analogWrite() on the brightness pin.

This is all doable in loop() by checking the if delay number of milliseconds have passed (as set by the speed pot) and if so toggle the LED (and reset your timer ms count to zero for the next delay period). You would always just analogWrite() the new PWM value obtained from the brightness pot.

You're getting there. Keep the faith. This isn't something you just magically become able to do overnight, it's more like learning to play a musical instrument, no shortcuts, just practice, practice, practice :)

the fader will complete sooner if the magnitude is lower

i didn't attempt to redo what your code does, i didn't wnat to reverse engineer to understand exactly what your code does.

i hoped by showing you how more primitve functions could presumably do the things that you are doing, that you could figure out how to rewrite your code without using delay. you may need to add some primitve functions

The 1st fade case can check a flag to break; at the end or not with the 2nd fade case (fade in or out) as the next one. Set the flag runs both, clear the flag stops the drop-thru.

Oh yes OP, if you don’t put breaks between cases, each case runs until a break executes or the switch-case ends. It is kind of neat to have one case init drop right into the next case and change the machine state to loop back into the next case or wherever.

Take care that one case does not overwrite variables used by another unless it’s supposed to.

For your immediate issue of varying both the led speed and brightness with each controlled by a pot, I put together a short example that does just that. The LED brightness pin is A5 and the LED speed (delay between each ON/OFF) is pin A6. The LED pin is 3 (D3). Check your pinout, only certain pins provide PWM through analogWrite().

I also use a ternary operator (a shorthand if ... else) which is structured test_clause ? true_action : false_action which can be used anywhere within your code. You can write the full if (test_clause) { true_action } else { false_action } if you like -- they are equivalent.

The short (tested) example is:

#define ADCBRIGHT  A5     /* brightness PWM pin */
#define ADCSPEED   A6     /* blink delay (speed) pin */

#define LEDPIN      3     /* LED pin - must allow PWM, check pinout */

/* enum for ledstate OFF/ON (you could just use 0/1, or false/true */
typedef enum { OFF, ON } ledstate_t;

void setup()
{
  /* no setup required for analogRead/analogWrite */
}

void loop()
{
  /* real-time loop timing varaibles */
  static unsigned long  rt_start_ms = millis(); 
  unsigned long rt_now_ms = millis(),
                rt_diff_ms = rt_now_ms - rt_start_ms;

  static ledstate_t ledstate = OFF;                   /* led on/off */
  uint8_t dc = analogRead (ADCBRIGHT) >> 2;           /* duty-cycle (raw/4) */
  uint16_t delay = analogRead (ADCSPEED);             /* read of led delay */
  
  /* LED update, using 0 - 1023 ms raw ADC value from ADCBRIGHT */
  if (rt_diff_ms >= delay) {
    /* toggle led state each time ADCSPEED ms elapses */
    ledstate = ledstate == OFF ? ON : OFF;
    /* write duty-cycle if ledstate is ON, 0 otherwise */
    analogWrite (LEDPIN, (ledstate == ON) ? dc : 0);
    /* reset start ms of timing period to now */
    rt_start_ms = rt_now_ms;
  }
}

(note: you could use (uint8_t)OFF instead of 0 in the analogWrite (LEDPIN, ...), but the cast is needed to avoid the mixed value enum ordinal warning that will be emitted by the compiler. rule Always compile with full-warnings enabled and do not accept code until it compiles without warning)

Hope this helps, let me know if you have questions.