0

lesson 3 - delay on reading button press
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

lesson 3 - delay on reading button press

by ih8gates on Mon Feb 04, 2008 3:57 pm

i'm using lesson 3 as a good test-bed to learn about case statements and functions in C/Arduino. I'm discovering, though, that putting a lot of delays into my sketch makes it 'sluggish'.

for ex, if i'm going through a long sequence of LED animations (like the wave), the code that checks for button-presses doesn't get executed while the 'animation' is being executed, so you basically need to hold on to the button for a bit until the animation completes.

i guess the question that i'm getting around to is "how do you do this if you know what you're doing?" what's the best method for spacing out commands with delays while being responsive to inputs(button presses)?

is there a way to compartmentalize a set of commands (an animation) into a function that'll run 'on its own' outside of the loop{} construct?
ih8gates
 
Posts: 33
Joined: Mon Jan 28, 2008 7:56 pm
Location: Raleigh, NC

by magician13134 on Mon Feb 04, 2008 4:23 pm

In a 'for' loop, you can put some kind of statement, such as
Code: Select all | TOGGLE FULL SIZE
if ((double)loopingVar / (double)5 == (double)(loopingVar / 5)){ // Or use modulo
     // Read your button or do whatever else here
}


If you do that, it'll check for the button enough to make it unnoticeable, but not enough to interfere with fading.

It works much the same in a 'while' loop, but you'll need code similar to this:
Code: Select all | TOGGLE FULL SIZE
int count = 0; // deceleration
while (whatever){
     count++;// add one to count
     if (count == 5){ // once every 5 loops, change this to fit your needs
          count = 0; // reset count
          // Do whatever else, like read the button
     }
}


Hope that's what you were looking for.




For those of you confused with my if statement in the 'for' loop, the reason that works is this:
Code: Select all | TOGGLE FULL SIZE
(double)10 = 10.0
(double)5 = 5.0
(double)(10 / 5) = (double)(2) = 2.0

So (double)10 / (double)5 = 2.0 = (double)(10 / 5) = 2.0

However:
Code: Select all | TOGGLE FULL SIZE
(double)10 = 10.0
(double)4 = 4.0
(double)(10 / 4) = (double)(2) = 2.0 // Why? Because (10 / 4) is 2.5, but since it's in parenthesis, the .5 removed BEFORE the double is applied, then the double just appends a .0


So (double)10 / (double)4 = 2.5 != (double)(10 / 4) = 2.0
magician13134
 
Posts: 1119
Joined: Wed Jun 13, 2007 9:17 am
Location: Ann Arbor

by darus67 on Mon Feb 04, 2008 5:09 pm

The REAL way to do it would be to use an interrupt.

Check out: attachInterrupt and detachInterrupt

in the Arduino software Extended Reference

This lets you define a function that will be called immediately when the button
is pressed. Whatever else is happening will be put in suspended animation
until your interrupt handler returns. Consequently, your interrupt handler
should be kept as short as possible.
"He's just this guy. You know?"
darus67
 
Posts: 246
Joined: Wed Sep 26, 2007 10:25 pm
Location: Minnesota, USA

by magician13134 on Mon Feb 04, 2008 8:48 pm

Dang it! I wish I had known about those before now!
magician13134
 
Posts: 1119
Joined: Wed Jun 13, 2007 9:17 am
Location: Ann Arbor

thanks!

by ih8gates on Mon Feb 04, 2008 9:42 pm

thanks all!

yeesh - so much to learn. not sure i'm in love with C (perl lets me be a lot... er... sloppier)
ih8gates
 
Posts: 33
Joined: Mon Jan 28, 2008 7:56 pm
Location: Raleigh, NC

Re: lesson 3 - delay on reading button press

by tomp on Tue Feb 05, 2008 12:43 am

ih8gates wrote:i'm using lesson 3 as a good test-bed to learn about case statements and functions in C/Arduino. I'm discovering, though, that putting a lot of delays into my sketch makes it 'sluggish'.

for ex, if i'm going through a long sequence of LED animations (like the wave), the code that checks for button-presses doesn't get executed while the 'animation' is being executed, so you basically need to hold on to the button for a bit until the animation completes.

i guess the question that i'm getting around to is "how do you do this if you know what you're doing?" what's the best method for spacing out commands with delays while being responsive to inputs(button presses)?

is there a way to compartmentalize a set of commands (an animation) into a function that'll run 'on its own' outside of the loop{} construct?


Here's what I did to avoid the sluggishness you're talking about. Basically, I broke each blinky-light mode up into steps, and arranged it so that the loop() function always checked the state of the button and then carried out the next step of the current mode. This way, the button is checked regularly while your blinky LED pattern is playing. Hopefully a bit of the the code will make clear what I'm trying to describe...

Code: Select all | TOGGLE FULL SIZE

void loop() {
  if (button_pressed()) {
      lightMode = lightMode + 1; // go to the next light mode
      modeStep = 0;
  }
 
  if (lightMode == 0) {
    lights_off();
  }
  else if (lightMode == 1) {
    lights_on();
  }
  else if (lightMode == 2) {
    blink_lights();
  }
  else if (lightMode == 3) {
    alt_blink();
  }
  else if (lightMode == 4) {
    left_right();
  }
  else if (lightMode == 5) {
    wave_out();
  }
  else if (lightMode == 6) {
    grow_out();
  }
  else if (lightMode == 7) {
    wave_in_out();
  }
  else if (lightMode == 8) {
    wave_in();
  }
  else if (lightMode == 9) {
    fill_in();
  }
  else if (lightMode == 10) {
    bounce();
  }
  else if (lightMode == 11) {
    scan();
  }
  else if (lightMode == 12) {
    slosh();
  }
  else {
    lightMode = 0;
  }
}

int button_pressed() {
  int switchState = digitalRead(switchPin);
  if (switchState == HIGH && lastSwitchState == LOW) { // button was pressed
    delay(10);  // wait a few milliseconds to make sure the switch state is stable
    switchState = digitalRead(switchPin);
    if (switchState == HIGH) {
      lastSwitchState = switchState;
      return 1;
    }
  }
  lastSwitchState = switchState;
  return 0;
}


// The various blinky light modes...

void lights_off() {
    digitalWrite(ledPin1, LOW);
    digitalWrite(ledPin2, LOW);
    digitalWrite(ledPin3, LOW);
    digitalWrite(ledPin4, LOW);
    digitalWrite(ledPin5, LOW);
}
   
void lights_on() {
    digitalWrite(ledPin1, HIGH);
    digitalWrite(ledPin2, HIGH);
    digitalWrite(ledPin3, HIGH);
    digitalWrite(ledPin4, HIGH);
    digitalWrite(ledPin5, HIGH);
}
   
void blink_lights() {
  if (modeStep == 0) {
    lights_on();
  } else {
    lights_off();
  }
  modeStep = (modeStep + 1) % 2;
  delay(100);
}

// (cut out a bunch of modes...)
   
void fill_in() {
  if (modeStep == 0) {
    lights_off();
  }
  else if (modeStep == 1) {
    digitalWrite(ledPin1, HIGH);
    digitalWrite(ledPin5, HIGH);
  }
  else if (modeStep == 2) {
    digitalWrite(ledPin2, HIGH);
    digitalWrite(ledPin4, HIGH);
  }
  else if (modeStep == 3) {
    digitalWrite(ledPin3, HIGH);
  }
  modeStep = (modeStep + 1) % 4;
  delay(200);
}

void scan() {
  if (modeStep == 0) {
    lights_off();
    digitalWrite(ledPin1, HIGH);
  }
  else if (modeStep == 1) {
    digitalWrite(ledPin1, LOW);
    digitalWrite(ledPin2, HIGH);
  }
  else if (modeStep == 2) {
    digitalWrite(ledPin2, LOW);
    digitalWrite(ledPin3, HIGH);
  }
  else if (modeStep == 3) {
    digitalWrite(ledPin3, LOW);
    digitalWrite(ledPin4, HIGH);
  }
  else if (modeStep == 4) {
    digitalWrite(ledPin4, LOW);
    digitalWrite(ledPin5, HIGH);
  }
  else if (modeStep == 5) {
    digitalWrite(ledPin5, LOW);
    digitalWrite(ledPin4, HIGH);
  }
  else if (modeStep == 6) {
    digitalWrite(ledPin4, LOW);
    digitalWrite(ledPin3, HIGH);
  }
  else if (modeStep == 7) {
    digitalWrite(ledPin3, LOW);
    digitalWrite(ledPin2, HIGH);
  }
  modeStep = (modeStep + 1) % 8;
  delay(100);
}

// etc, etc...

tomp
 
Posts: 57
Joined: Sun Nov 25, 2007 9:07 pm
Location: South Orange, NJ

Re: lesson 3 - delay on reading button press

by ih8gates on Tue Feb 05, 2008 7:31 am

TomP wrote:Here's what I did to avoid the sluggishness you're talking about.


Actually, I did something similar to this... I set up a modulo for the various modes, then created a bunch of functions for each mode.

that's where I discovered the issue since some of the modes took a long time.

If I understand correctly, the best practice would be to use an interrupt to poll for button presses, but as a fall-back, you could insert tiny "check the button" commands into your existing functions (using modulo over the loop variable or similar) to check during a long animation.
ih8gates
 
Posts: 33
Joined: Mon Jan 28, 2008 7:56 pm
Location: Raleigh, NC

Re: lesson 3 - delay on reading button press

by tomp on Tue Feb 05, 2008 9:41 am

ih8gates wrote:Actually, I did something similar to this... I set up a modulo for the various modes, then created a bunch of functions for each mode.

that's where I discovered the issue since some of the modes took a long time.

The code I pasted in above shows how to code this up so that the length of each mode doesn't matter. Each mode is broken up into individual little steps, and in each pass through the loop() function only one of those steps is carried out; the 'modeStep' variable keeps track of what step you're on. As a result, the maximum time between button checks is the length of a step - typically just 100 or 200ms - regardless of how long it takes to play through the full cycle of steps for a given mode.

As for the idea of using interrupts to check for button presses, I don't think that's as easy as it sounds. A big part of lesson three was learning how to read a button cleanly despite the fact that buttons are bouncy. If you had an interrupt trigger every time the button bounced, I can imagine you'd have a hard time handling it correctly. A cardinal rule of handling interrupts is to do as little as possible in the interrupt handler, so control returns to the main code right away. In particular, you don't want your interrupt handler to be interrupted, itself. The button-reading strategy proposed in lesson three - to wait 10ms or so after a button-down to make sure it stays down - is something you couldn't get away with in an interrupt handler. To use interrupts, I think you'd either have to debounce the button in hardware (with caps?) or come up with a different debouncing algorithm.

tomp
 
Posts: 57
Joined: Sun Nov 25, 2007 9:07 pm
Location: South Orange, NJ

by ih8gates on Tue Feb 05, 2008 10:33 am

aah. thanks. I see now how you're stepping the modeStep - missed that before. yeah - that's a good way to handle that issue. thanks.
ih8gates
 
Posts: 33
Joined: Mon Jan 28, 2008 7:56 pm
Location: Raleigh, NC

by mtbf0 on Tue Feb 05, 2008 11:24 am

there is a function millis () available on the arduino that returns the number of milliseconds as an unsigned long integer since the chip started up. this will overflow in just under 50 days, so if you remember to reset your device every 6 weeks you can keep track of your own time.

here is an untested version of the debounce routine coded without waits. your other functions would have to be rewritten in a similar manner.


Code: Select all | TOGGLE FULL SIZE
int switchState;
int lastSwitchState = LOW;
unsigned char debounce = 0;
unsigned long debounceTime;

int button_pressed() {
  switchState = digitalRead(switchPin);
  if (debounce) {
    if (debounceTime < millis ()) {   // debounce time expired?
      debounce = 0;
      lastSwitchState = switchState;
      if (switchState == HIGH) return 1;
    }
  }
  else {
    if (switchState == HIGH && lastSwitchState == LOW) { // button was pressed
      debounce = 1;
      debounceTime = millis () + 11;  // delay will be >10 and <11 ms.
    }
  }
  return 0;
}




this code will fail spectacularly between 49 and 50 days. or between 99 and 100 days. put another way "...you'll regret it. Maybe not today. Maybe not tomorrow. But, shoon, and for the resht of your life."

this can be corrected if you are certain that none of your code will run longer than your minimum delay by changing the test (debounceTime < millis()) to (debounceTime == millis()). might be a safe bet, but if not the code will likely fail spectacularly in far less than 49 days.

but, y'know... millis() >= debounceTime, (not the same as debounceTime < millis(), in case milliseconds have overflowed), might be the best bet.

also, please note that an arduino millisecond is actually 1.024 ms, so don't try building a clock with this thing.

and, in addition, the delay for delay(i) will be anywhere between i-1 and i milliseconds.
User avatar
mtbf0
 
Posts: 1645
Joined: Sat Nov 10, 2007 12:59 am
Location: oakland ca

by caitsith2 on Tue Feb 05, 2008 2:29 pm

mtbf0 wrote:
Code: Select all | TOGGLE FULL SIZE
int switchState;
int lastSwitchState = LOW;
unsigned char debounce = 0;
unsigned long debounceTime;

int button_pressed() {
  switchState = digitalRead(switchPin);
  if (debounce) {
    if (debounceTime < millis ()) {   // debounce time expired?
      debounce = 0;
      lastSwitchState = switchState;
      if (switchState == HIGH) return 1;
    }
  }
  else {
    if ((switchState == HIGH && lastSwitchState == LOW) || (switchState == LOW && lastSwitchState == HIGH)) { // button was pressed or released
      debounce = 1;
      debounceTime = millis () + 11;  // delay will be >10 and <11 ms.
    }
  }
  return 0;
}






This code is already going to fail. There is nothing to set lastSwitchState = LOW at all. As such, the button, based on your initial code state, will work ONLY once, then it won't work again till full hardware reset.

An IF statement was changed to
Code: Select all | TOGGLE FULL SIZE
f ((switchState == HIGH && lastSwitchState == LOW) || (switchState == LOW && lastSwitchState == HIGH)) { // button was pressed or released
. This should fix the bug.

caitsith2
 
Posts: 217
Joined: Thu Jan 18, 2007 11:21 pm

Re: lesson 3 - delay on reading button press

by Entropy on Tue Feb 05, 2008 9:41 pm

TomP wrote:
ih8gates wrote:Actually, I did something similar to this... I set up a modulo for the various modes, then created a bunch of functions for each mode.

that's where I discovered the issue since some of the modes took a long time.

The code I pasted in above shows how to code this up so that the length of each mode doesn't matter. Each mode is broken up into individual little steps, and in each pass through the loop() function only one of those steps is carried out; the 'modeStep' variable keeps track of what step you're on. As a result, the maximum time between button checks is the length of a step - typically just 100 or 200ms - regardless of how long it takes to play through the full cycle of steps for a given mode.

As for the idea of using interrupts to check for button presses, I don't think that's as easy as it sounds. A big part of lesson three was learning how to read a button cleanly despite the fact that buttons are bouncy. If you had an interrupt trigger every time the button bounced, I can imagine you'd have a hard time handling it correctly. A cardinal rule of handling interrupts is to do as little as possible in the interrupt handler, so control returns to the main code right away. In particular, you don't want your interrupt handler to be interrupted, itself. The button-reading strategy proposed in lesson three - to wait 10ms or so after a button-down to make sure it stays down - is something you couldn't get away with in an interrupt handler. To use interrupts, I think you'd either have to debounce the button in hardware (with caps?) or come up with a different debouncing algorithm.

Back when I was learning about microcontrollers as an undergrad, it was pretty rare for anyone to have code that ran in an infinite loop "as fast as possible" - It was far more common to set timers up to have an interrupt fire periodically that would handle various tasks. This way if your interrupt had anything time-dependent, you could ensure that the interval between firings was consistent.

So the usual switch/button debouncing algorithm was to have a state machine in a periodic interrupt handler that would check your button(s) and only take action if it stayed in a given state (on/off) for more than a few interrupt cycles in a row.

For example, a program I'm working on for work processes a few button inputs. I have an interrupt that is set to fire every millisecond for miscellaneous tasks, and each button has a counter associated with it. When a button is sensed as not matching its last state, the counter starts incrementing. If it reaches 10 (i.e. button/switch has been in a new state for 10 ms continuously), the state variable that tracks the debounced button state is changed. If the button changes back before 10 continuous interrupt fires occur, the counter is reset to zero.
Entropy
 
Posts: 472
Joined: Tue Jan 08, 2008 12:43 am
Location: Owego, NY USA

Re: lesson 3 - delay on reading button press

by tomp on Tue Feb 05, 2008 10:07 pm

Entropy wrote:Back when I was learning about microcontrollers as an undergrad, it was pretty rare for anyone to have code that ran in an infinite loop "as fast as possible"


Nonetheless, that's the model for the Arduino. If you're going to advise people to use interrupts to solve their Arduino problems, it would be helpful for you to take that into account.

...the usual switch/button debouncing algorithm was to have a state machine in a periodic interrupt handler that would check your button(s) and only take action if it stayed in a given state (on/off) for more than a few interrupt cycles in a row.

For example, a program I'm working on for work processes a few button inputs. I have an interrupt that is set to fire every millisecond for miscellaneous tasks, and each button has a counter associated with it. When a button is sensed as not matching its last state, the counter starts incrementing. If it reaches 10 (i.e. button/switch has been in a new state for 10 ms continuously), the state variable that tracks the debounced button state is changed. If the button changes back before 10 continuous interrupt fires occur, the counter is reset to zero.


That sounds like an eminently reasonable design, but simply suggesting that people "use interrupts" doesn't make it clear how they need to adjust their sketches to use them effectively. Maybe you could publish some tutorials on the Arduino playground site to show people how to use interrupts this way?

Edited to addMy bad - it was darus67 who suggested people use interrupts - not you. I appreciate your taking the time to explain how to use them correctly.
Last edited by tomp on Tue Feb 05, 2008 11:15 pm, edited 1 time in total.

tomp
 
Posts: 57
Joined: Sun Nov 25, 2007 9:07 pm
Location: South Orange, NJ

by tomp on Tue Feb 05, 2008 10:43 pm

mtbf0 wrote:there is a function millis () available on the arduino that returns the number of milliseconds as an unsigned long integer since the chip started up. this will overflow in just under 50 days,

Apparently, for reasons I don't understand, the millis() function overflows every 9 hours. You don't get a full unsigned long full of milliseconds.

this code will fail spectacularly between 49 and 50 days.

It only fails if you're so spectacularly unlucky as to hit a button within the last 10 milliseconds before the counter rolls over. (...assuming you fix the bug caitsith2 pointed out.) So, although it's not "robust", it's actually pretty unlikely to fail, ever. (Extra credit if you can calculate the probability of a failure, for a hypothetical user who hits the button every two seconds for nine hours...)

tomp
 
Posts: 57
Joined: Sun Nov 25, 2007 9:07 pm
Location: South Orange, NJ

by mtbf0 on Tue Feb 05, 2008 10:53 pm

caitsith2 wrote:An IF statement was changed to
Code: Select all | TOGGLE FULL SIZE
f ((switchState == HIGH && lastSwitchState == LOW) || (switchState == LOW && lastSwitchState == HIGH)) { // button was pressed or released
. This should fix the bug.


mea culpa. teach me to cut code out of someone else's post.

however...
Code: Select all | TOGGLE FULL SIZE
if (switchState != lastSwitchState) { // etc

... would be prettier and efficientier.

and there's other stuff that troubles me, like one generally wires a switch with a pullup, so this thing returns TRUE for a switch release. and the code just assumes the switch will have stopped bouncing in 10ms. this, too, i lifted from another post, although it runs contrary to my own (limited) experience.

the whole thing is just kind of naive. i was doing laundry. i don't know...
User avatar
mtbf0
 
Posts: 1645
Joined: Sat Nov 10, 2007 12:59 am
Location: oakland ca

Please be positive and constructive with your questions and comments.


cron