Holding a button/relay

Hi Guys,

Is there a way to ‘hold’ a button? For example, the buttons I have are on or off with effectively no delay. In the code, is there a way to to ‘pretend’ that the button has been held on for 1 second for example?

Is this best done using the code or using hardware (a different button or relay).

Thanks in advance,

Kyle

Sure, sense a button press, do an action for a second, then stop the action. Best done with blink without delay and a 'flag' Something like this:

// declare all variables used below, time elements are unsigned long
void setup(){
pinMode (pinX, INPUT_PULLUP); // wire pinX to connect to Gnd when button is pressed
}

void loop(){
currentMillis = millis();
// check if button is pressed to start an action
if (digitalRead(pinX) == LOW && pinXtimerRunning == 0){
pinXtimerRunning = 1;
pinXstartTime = currentMillis;
// start whatever action is to occur
}
// check if time to stop
if (pinXtimerRunning == 1){
pinXelapsedTime = currentMillis - pinXstartTime;
  if (pinXelapsedTime >= pinXonDuration){
  // stop whatever action is to occur
  pinXtimerRunning = 0;
  }
}
}

Hey!

Thanks very much! I'll try to implement this into my code asap. I'll also explain the application.

what does 'unsigned long' mean? At this point in my learning, I think I've got my head round the rest of the code you posted.

Thanks again,

Kyle

Can't google? unsigned long

Ah!

I just realised how many variables there are here! Is this lest of varibales correct?

currentMillis
pinXtimerRunning
pinXstartTime
pinXelapsedTime
pinXonDuration

If so, I’m not sure what to assign for each. Here’s what I have so far but I know it’s far from working:

#include "FastLED.h"


#define NUM_LEDS 15

CRGB leds[NUM_LEDS];

const int indicatorPin = 2;    
int currentMillis = 0;
int indicatorPintimerRunning = 1000;
int indicatorPinstartTime = 0;
int indicatorPinelapsedTime = 1000;
int indicatorPinonDuration = 1000;

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 loop(){
currentMillis = millis();
// check if button is pressed to start an action
if (digitalRead(indicatorPin) == LOW && indicatorPintimerRunning == 0){
indicatorPintimerRunning = 1;
indicatorPinstartTime = currentMillis;

FastLED.showColor(CRGB::Red); 

// 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
  indicatorPintimerRunning = 0;
  }
}
}

Many thanks,

Kyle

J-M-L: Can't google? unsigned long

The terminology used on a lot of the Arduino pages is still beyond me so the Unsigned Long page doesn't make much sense to me nor does it help me understand it's purpose unfortunately. I really wish I could understand all this so a lot of the simpler things would makes sense too.

Thanks again,

Kyle

kylefoster: The terminology used on a lot of the Arduino pages is still beyond me so the Unsigned Long page doesn't make much sense to me nor does it help me understand it's purpose unfortunately. I really wish I could understand all this so a lot of the simpler things would makes sense too.

Thanks again,

Kyle

On arduino Integers are your primary data-type for number storage. They are declared with int type, like int v;

On the Arduino Uno (/ ATMega based boards) an int is stored as a 16-bit (2-byte) value. This yields a range of -32,768 to 32,767 (minimum value of -2^15 and a maximum value of (2^15) - 1).

This range might not be enough for what you want to do. If you know you don't need negative values, then you can tell the compiler to use the 16 bits to represent only positive numbers. This is done by adding the unsigned keyword to the variable declaration: unsigned int. So unsigned integers are the same as ints in that they store a 2 byte value but Instead of storing negative numbers they only represent positive values, yielding a useful range of 0 to 65,535 (2^16) - 1).

The range might not be enough and if you want to go above 65535 then you need more bits to represent your number. That's what the long type is about. Long variables are extended size variables for number storage, and store 32 bits (4 bytes), from -2,147,483,648 to 2,147,483,647.

If 2 billions is not enough and you know you don't deal with negative numbers (like measuring a duration) then the unsigned keyword can be used too. Unsigned long variables are extended size variables for number storage, and store 32 bits (4 bytes). Unlike standard longs unsigned longs won't store negative numbers, making their range from 0 to 4,294,967,295 (2^32 - 1).

The millis() function Returns the number of milliseconds since the Arduino board began running the current program. This number will overflow (go back to zero), after approximately 50 days because it counts as an unsigned long.

Hope this helps

Thanks again for your time JML,

That does make a bit more sense.

Kyle

int currentMillis = 0;
int indicatorPintimerRunning = 1000;
int indicatorPinstartTime = 0;
int indicatorPinelapsedTime = 1000;
int indicatorPinonDuration = 1000;

So if you understand a bit more, don't declare your time measurement variables as int... CurrentMillis should clearly be an unsigned long, right? Same for elpasedTime and duration

indicatorPintimerRunning Is just an indicator, like true or false, 1 or 0. Don't initialize it with 1000. Initialize it to false, 0. It can be a Boolean.

Thanks JML,

I threw 1000 in for a couple to illustrate that I didn't know what to put there which has worked I suppose.

Now that you mention the time variables, it makes more sense to me generally.

I'll try that now,

thanks again,

Kyle

FastLED.showColor(CRGB::Red);

Will you be stopping this after the second is up?

Data types, change to these:

const byte indicatorPin = 2;   // poor name - should be like buttonPin, that's its use as an input
unsigned long currentMillis;             // time related variables need to be unsigned long, value determined by program
byte indicatorPintimerRunning = 0;    // flag, 0 = not running, 1 = running  
 unsigned long indicatorPinstartTime;     // value determined by program    
 unsigned long indicatorPinelapsedTime;   // value determined by program
unsigned long indicatorPinonDuration = 1000UL;     // UL may not be needed, but makes it clear
typedef decltype(millis()) Millis_Time;

Millis_Time currentMillis = 0;

have fun picking that apart.

The general pattern for timing intervals is:

1) Store the beginning of the interval you want to time into a variable.

buttonHoldIntervalStart = millis()

2) compare the difference between the current time and start time against the interval length

 if( millis() - buttonHoldIntervalStart >= buttonHoldIntervalLength )

buttonHoldInterval would be how long you want the interval to be, 1000 ms in your example.

3) If the condition evaluates true, either disable the interval from future checks or increment the start time by the interval. Then do your action. If the condition is false, do nothing.

buttonHoldIntervalStart += buttonHoldIntervalLength;

Thanks guys,

To be honest, this code is more complex than I’ve dealt with so far and is beyond my little brain to break down but I’m getting there:

#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;

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 loop(){
currentMillis = millis();
// check if button is pressed to start an action
if (digitalRead(indicatorPin) == HIGH && indicatorPintimerRunning == 0){
indicatorPintimerRunning = 1;
indicatorPinstartTime = currentMillis;

FastLED.showColor(CRGB::Green); 

// 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;
  }
}
}

At the moment, the button just causes my whole let strip to turn red for 1 second (since that’s what I’ve asked it to do) and it seems to be working. Is there anything in the code that doesn’t seem right? At the top of the void loop, I had to change the digital read from LOW to HIGH since it caused the opposite effect on my test strip.

I need to add my actual function code into this (a basic cyclone/sweeping led) but in the meantime I thought I’d ask if the button state can be read within the 1 second that this is being executed? I’ll post a example of what I mean based on a video of my leds later to explain what I mean more clearly.

Thanks again guys, you’re wizards!

Kyle

At the top of the void loop, I had to change the digital read from LOW to HIGH since it caused the opposite effect on my test strip.

That means you probably did not wire the button correctly. Your button should connect the pin to ground when pressed, thus be LOW when pressed.

Thanks JML,

That’s sorted now.

So now I’ve had a go with millis(), it’s probably a good idea to show you how far I got before.

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 seperate 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:

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:

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?

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

Kyle

Hi Guys,

Can anyone help with this?

It seems like such a simple thing to do but I'm pulling my hair out!

Thanks again!

Kyle

if (indicatorPintimerRunning == 1){
indicatorSweep();
...

Thanks a lot Evan!

So I understand it, is this code asking 'if button is held for 1 second, perform indicatorsweep;'?

If so, I'd like it to be something more like 'if button is pressed, button press = 1 second (regardless of how long button press is).

Does that make sense?

Thanks again for the reply,

Kyle

isn't actually emulating holding the button for 1 second.

If so, I'd like it to be something more like 'if button is pressed, button press = 1 second (regardless of how long button press is).

Does that make sense?

Not really.

You want to detect the instant when a button is pressed, and following that, you want to ignore the release time(whether or not it is more or less than one second), but pass the value of one second to what? What do you want to happen for one second?

Try again in words to describe exactly what you want to happen.

If the button is pressed (for an instant or longer), set indicatorPintimerRunning to 1. Keep indicatorPintimerRunning (aka, buttonPressed) set for one second. While buttonPressed ==1, do whatever needs to happen while it looks like the button was held.