Turn a single button press into 1 second?

Hi Guys,

New here and trying to learn some coding and some led stuff. I’m a little stuck with trying to make a single button press last for a desired amount of time so here’s my project so far!

I’ve been having a go with millis() but it’s probably a good idea to show you how far I got before trying to use this function since the coding language is a little beyond me at the moment hence posting here!

Here’s my code without the Millis. It simply has 2 buttons which control one strip. One button acts as a turn signal ‘sweep’ the other acts as a brake light which I’m trying ‘mix’ with the turn signal rather than having separate strips that perform the two tasks:

#include "FastLED.h"


#define NUM_LEDS 16

CRGB leds[NUM_LEDS];

const int indicatorPin = 2;    
int indicatorState = 0;         
const int brakePin = 1;
int brakeState = 0;

int currentIndicatorLED = 0;

void setup() { 

  
  pinMode(indicatorPin, INPUT);
  pinMode(brakePin, INPUT);
  FastLED.addLeds<WS2812B, 6, RGB>(leds, NUM_LEDS);
  FastLED.addLeds<WS2812B, 5, RGB>(leds, NUM_LEDS);
  LEDS.setBrightness(50);
}

 void indicatorSweep()
 
{
  if (currentIndicatorLED < NUM_LEDS)
  {
    
    leds[currentIndicatorLED] = CRGB(255, 0, 0);
       
    currentIndicatorLED++;

 // Deal with the other leds after the current lit indicator led
    for (int led = currentIndicatorLED; led < NUM_LEDS; led++)
    { 
      // Are the brakes on ?
      if (brakeState == HIGH)
      {
         // Light the led as a brake
         leds[led] = CRGB(0, 255, 0);        
      }
      
      else
      
      {
         // Blank the led as there is no brake
         leds[led] = CRGB(0, 0, 0);
    }

    
    FastLED.show(); 
       
    }
  }
  
  else 
  
  {

    
    currentIndicatorLED = 0;

  }

  delay(40);
 
}


void loop() {

  indicatorState = digitalRead(indicatorPin);
  brakeState = digitalRead(brakePin);
  
  if (indicatorState == HIGH) {

    indicatorSweep();
   
  } else {
    
    FastLED.clear();
    FastLED.show();

    currentIndicatorLED = 0;
  }
}

Here’s a video of this in action, sorry for the audio:

[https://www.youtube.com/watch?v=jvzaoZ6Mv_o

I made the turn signal green to see it clearly but the IndicatorSweep() function is a loop that deals with 1 led per loop and then moves onto the next one. This allows the button state to be read at each led in turn which allows the brake light to jump in at any time. The only drawback (and also the last hurdle for my project) is that when you let go of the button, the sweep ceases. I’d like the sweep to go all the way to the end with 1 button press. I’ve tried while loops, interrupts, switch/case statements and then realised all I need to do is emulate holding the button down for 600ms (15 leds x 40ms between leds).

Now, here’s my attempt with millis(). At the moment, I’m only testing the green sweep part:

#include "FastLED.h"


#define NUM_LEDS 15

CRGB leds[NUM_LEDS];

const int indicatorPin = 2;    
unsigned long currentMillis;
byte indicatorPintimerRunning = 0;
unsigned long indicatorPinstartTime;
unsigned long indicatorPinelapsedTime;
unsigned long indicatorPinonDuration = 1000;
int currentIndicatorLED = 0;


void setup() {   
  
  FastLED.addLeds<WS2812B, 6, RGB>(leds, NUM_LEDS);
  pinMode (indicatorPin, INPUT_PULLUP); // wire pinX to connect to Gnd when button is pressed
}

void indicatorSweep()
 
{
  if (currentIndicatorLED < NUM_LEDS)
  {
    
    leds[currentIndicatorLED] = CRGB(255, 0, 0);
    FastLED.show();
    
    currentIndicatorLED++;
  }
  else
  {
    
    FastLED.clear();
    FastLED.show();                           
    delay(100);        

    currentIndicatorLED = 0;
  }

  delay(35);
 
}

void loop(){
currentMillis = millis();
// check if button is pressed to start an action
if (digitalRead(indicatorPin) == LOW && indicatorPintimerRunning == 0){
indicatorPintimerRunning = 1;
indicatorPinstartTime = currentMillis;

indicatorSweep();

// start whatever action is to occur
}
// check if time to stop
if (indicatorPintimerRunning == 1){
indicatorPinelapsedTime = currentMillis - indicatorPinstartTime;
  if (indicatorPinelapsedTime >= indicatorPinonDuration){
  // stop whatever action is to occur
  FastLED.showColor(CRGB::Black); 
  
  indicatorPintimerRunning = 0;
  }
}
}

Here’s a vid of this in action:

[https://www.youtube.com/watch?v=wX7CJS1p1Hk

You can see that at the moment, it’s performing the loop for 1 second (activate first led, wait 1 second and activate the next led) and isn’t actually emulating holding the button for 1 second.

Like I said, I’m struggling with the terminolgy of this code to understand how to modify it so I’m a little stuck.

Do you guys think it’s possible to use a variant of the millis() setup to achieve what I’m after? If so, how? If not, can ayone think of anything else I could do to achieve this?

Thanks in advance guys, I’m very excited about finishing this one!

Kyle](https://www.youtube.com/watch?v=wX7CJS1p1Hk)](https://www.youtube.com/watch?v=jvzaoZ6Mv_o)

Hi Kyle,

What you are starting to dive into is known as cooperative multitasking since you have 2 independent asynchronous tasks that need to be coordinated by a microcontroller that can only execute serially. For cooperative multitasking to work, you need to break down all your events into functions or groups of functions that take as little time as possible and then organize them in a main loop where you can control the time or events that cause them to execute.

Have a look at this pseudocode that I quickly threw together and realize that it’s only to help conceptualize a solution.

while(1)
{
//Use a button press to turn on the sweeping display
    if(Sweep_state == INACTIVE && Sweep_button == PRESSED)
    {
        Sweep_state = ACTIVE;
    }
        
//Check if Sweeping is active and that we have made it to the next time slot
//and update the LEDs.  If we have displayed all the LEDS, mark
//the Sweep_state as INACTIVE
    if(Sweep_state == ACTIVE && current_time() - last_sweep_time >= LED_UPDATE_TIME)
    {
        Update_sweep_display();
        last_sweep_time = current_time();

        if(currentSweepLED >= NUM_LEDS)
        {
            Clear_sweep_display();
            Sweep_state = INACTIVE;
        }
    }


//Display the stop pattern whenever the button is pushed
//Otherwise clear the stop pattern whenever the button is released
    if(Stop_button == PUSHED)
    {
        display_stop_pattern();
    }
    else
    {
        clear_stop_pattern();
    }

} //Close while(1)

My approach for your problem doesn’t involve timing the release of a button to finish the LED sweep but instead waits for your “SWEEP” routine to finish updating all of its LEDs. The while() loop only directly checks three things using if statements:

  1. Is the sweep inactive and the sweep button pushed?

  2. Is the sweep active and the change in time greater than or equal to the LED update time?

  3. Is the stop button pushed?

If #1 creates what we could call a virtual button that can be used for decision making elsewhere in the program. You can see that it only sets your sweep_state to active so releasing the button will not change it to inactive.

If #2 handles the updating of the LEDs in your sweep sequence. The if statement quickly checks to see if sweep_state is active and if the correct amount of time has passed for updating the LEDs. If the sequence isn’t active or the correct amount of time hasn’t passed, the Arduino continues on to other tasks in the while() loop. If the sequence is active and the correct amount of time has passed, it calls a routine to update your LED pattern and then stores the current time so it can be compared for updating the next LED. Since time is part of the if’s conditional checks, it’s necessary to remove the 40ms delay from your existing sweep routine and tweak it so that every time you run it, it will update only the next LED in your pattern. After storing the current time, it does a quick check to see if you’ve reached the end of your LED pattern. If it has, it calls a function to clear the LEDs and sets the virtual button Sweep_state to inactive.

If #3 only checks to see if the stop button is pressed. If it is, it calls a function to display the stop pattern otherwise it calls a function to clear the stop pattern.

Keep in mind that my solution is not optimized nor is it the only approach to solve your problem. I hope it gets you thinking about multitasking and how you need to break down your project into many small tasks that can be pieced together and coordinated by a higher level control loop.

Cheers,

-Bill

Hi Bill,

First of all, thanks so much for your time and I’m sorry for my late reply. I spent some time reading some more basic tutorials on C++ to hopefully help with the structure and the language (no promises).

Thanks for your very intelligent and thorough reply, it’s the most clear explanation of this approach I’ve heard which has really helped. I know you said that your code is simply to convey a concept which is great but I’ve used a lot of what you’ve written in my code just to help me understand it a little more and so I know where I’m going wrong in terms of the structure.

I think I also understand your logic when it comes to stepping away from my millis() example but I’ve done some reading on it anyway and I assume the time timed based variables you’ve written would still be unsigned longs? I’m not sure how to declare these? Do I need to declare millis() somewhere here?

I’ve also tried to read up about state changes and I’ve worked through some very basics examples:

[Using a Button to Change State

[State Change Detection

This has helped me to grasp things to some extent but putting all this together seems like rocket science to me still.

Since I’m still pretty stumped despite your clear explanation, my code is a mess but I thought it would be at least an idea to get the ball rolling and ask a couple questions based on your advice:

#include "FastLED.h"


#define NUM_LEDS 16

CRGB leds[NUM_LEDS];

const int indicatorPin = 2; 
const int brakePin = 1;   
int indicatorState = 0;         
int brakeState = 0;
int currentIndicatorLED = 0;
int sweepState = 0;
unsigned long lastSweepTime;
unsigned long currentTime;
unsigned long ledUpdateTime;

void setup() { 

  
  pinMode(indicatorPin, INPUT);
  pinMode(brakePin, INPUT);
  FastLED.addLeds<WS2812B, 6, RGB>(leds, NUM_LEDS);
  LEDS.setBrightness(50);

{
 
while 
{
  if(sweepState == LOW && indicatorPin == HIGH)
{

   sweepState = HIGH
 
}

 if(sweepState == HIGH && currentTime() - lastSweepTime >= ledUpdateTime)

 {
   indicatorSweep();
  lastSweepTime = currentTime();

  if(currentIndicatorLED >= NUM_LEDS)
  {
    leds[led] = CRGB(0, 0, 0);
    sweepState = LOW;
  }
 }

 


void indicatorSweep()

{
  
  if (currentIndicatorLED < NUM_LEDS)
  {
    
    leds[currentIndicatorLED] = CRGB(255, 0, 0);
       
    currentIndicatorLED++;

 // Deal with the other leds after the current lit indicator led
    for (int led = currentIndicatorLED; led < NUM_LEDS; led++)
    { 
      // Are the brakes on ?
      if (brakeState == HIGH)
      {
         // Light the led as a brake
         leds[led] = CRGB(0, 255, 0);        
      }
      
      else
      
      {
         // Blank the led as there is no brake
         leds[led] = CRGB(0, 0, 0);
    }

    
    FastLED.show(); 
       
    }
  }
  
  else 
  
  {

    
    currentIndicatorLED = 0;

  }

  delay(40);
 
}


void loop() {
  
  if (sweepState == HIGH) {

    indicatorSweep();
   
}

You mentioned removing the 40ms delay since we’re using time for conditional checks. Based on this, do I need to completely remove my ‘void indicatorSweep()’ function and implement this functionality into the while loop?:

Since time is part of the if’s conditional checks, it’s necessary to remove the 40ms delay from your existing sweep routine and tweak it so that every time you run it, it will update only the next LED in your pattern. After storing the current time, it does a quick check to see if you’ve reached the end of your LED pattern.

This makes a lot of sense and I’m trying my hardest to take all this in. If this is the case, what do we call when in our void loop? As you can tell, I’m very new to this so it’s confusing (but logical) to modify my code to this extent in order to achieve this last part of the functionality. That being said, it’s always been something I’ve needed, I just didn’t know how to deal with it.

You also mentioned breaking down the project into small tasks but I’m just not sure what else needs to be broken up and it seems the different parts of the code can be dropped into different places (such as breaking out my indicatorSweep() function into the while loop?

Thanks again Bill, at least I think this is a step in the right direction. Of course, ideally, you’d be able to walk me through this a bit at a time but I don’t expect you to spare that much time. Any more advice you have time for would be greatly appreciated!

Kyle](https://www.arduino.cc/en/Tutorial/StateChangeDetection)](https://www.youtube.com/watch?v=QslLAPNSEFc)

Sorry, I forgot to mention I’ve been reading about Boolean variables too and I’ve guessing a couple of my variables need to be declared in this way?

Here. Try this, it was originally designed to run a motor back and forth depending on button press. Keypresses on pins 2&3 are “extended” to one second each. The variable _buttonState goes true when the pushbutton on is pressed, then goes low one second later. So all you have to do is monitor the _buttonState[0] or [1] to do whatever task you need done while it’s true.
*_ <em>*_forwardTime[] = {1000, 1000}*</em> _* sets the length of time the state stays high.
*_ <em>*const int _inputs[] = {3, 4};*</em> _*
identifies the input pins
Have fun!
```
*// Constants
const int NUMBER_OF_CHANNELS = 2;

// Arduino pins used for input
const int _inputs = {2, 3};

// Time intervals per channel
const int _forwardTime = {1000, 1000};

// Global state
int _inMotion[NUMBER_OF_CHANNELS];
bool _lastState[NUMBER_OF_CHANNELS];
bool _buttonState[NUMBER_OF_CHANNELS];

void setup()
{
Serial.begin(115200);
for (int i=0; i < NUMBER_OF_CHANNELS; i++)
{
pinMode(_inputs[i], INPUT_PULLUP);
_lastState[i] = digitalRead(_inputs[i]);
_buttonState[i] = false;
}
}

void loop()
{
// 1 ms loop interval
delay(1);

bool currentState = LOW;
for (int i=0; i < NUMBER_OF_CHANNELS; i++)
{
// Detect an edge
currentState = digitalRead(_inputs[i]);
if (currentState != _lastState[i] )
{
// Is it a falling edge?
if (currentState == LOW)
{
// Output active
_buttonState[i] = true;
_inMotion[i] = _forwardTime[i];
char buffer[20];
sprintf(buffer,“%d ON”,i);
Serial.println(buffer);
}
}

// Update previous state
_lastState[i] = currentState;

// Now update motions
if (_inMotion[i] > 0 )
{
  // Decrement motion timer
  if ( --_inMotion[i] == 0 )
  {     
    // Timed out. Stop
    _buttonState[i] = false;
    char buffer[20];
    sprintf(buffer,"%d OFF",i);
    Serial.println(buffer);
  }
} 

}
}
_
```*_

Sorry for the delay in getting back to you. Saying my work schedule has been hectic is a gross understatement.

kylefoster:
You mentioned removing the 40ms delay since we’re using time for conditional checks. Based on this, do I need to completely remove my ‘void indicatorSweep()’ function and implement this functionality into the while loop?:

You don't have to remove your indicatorSweep() function but you should remove the delay(40) command from it. Timing is going to be handled outside of the indicatorSweep() function so you want it to update the LED pattern and exit as quickly as possible.

The millis() function can replace what I called currentTime() in my example code since it is keeping track of milliseconds since the board was turned on.

This makes a lot of sense and I’m trying my hardest to take all this in. If this is the case, what do we call when in our void loop? As you can tell, I’m very new to this so it’s confusing (but logical) to modify my code to this extent in order to achieve this last part of the functionality. That being said, it’s always been something I’ve needed, I just didn’t know how to deal with it.

Your loop() function is not needed for this program because the second "if" statement in my example while() loop does all the checking necessary for determining if it's time to update the LEDs or not. You just have to make sure that your indicatorSweep() function simply updates the next LED in your sequence and exits as fast as possible (no delay statements since that is being handled in the while loop).

You also mentioned breaking down the project into small tasks but I’m just not sure what else needs to be broken up and it seems the different parts of the code can be dropped into different places (such as breaking out my indicatorSweep() function into the while loop?

For your purposes, I don't think you will have to break down your indicatorSweep() function any further. What it needs to accomplish relative to the 40ms time between updates is miniscule so breaking it down really won't gain you anything.

-Bill