In the interest of introducing some "best (well, better) practices", try breaking your problem down into smaller parts that are more easily understood:
void loop() {
read_button_and_adjust_time();
maybe_flash_led();
}
OK so far, right? That was pretty trivial. But we've already made things easier; the led timing and the button timing will no longer be "mixed up."
The maybe_flash_led() comes pretty straight from blink_without_delay(), except it has code for "off"
int ledState = LOW; // ledState used to set the LED
long previousMillis = 0; // will store last time LED was updated
long interval = 1000; // interval at which to blink (milliseconds)
void maybe_flash_led() {
unsigned long currentMillis = millis();
if (interval == 0) { // turn off?
if (ledState == HIGH) {
ledState = LOW;
digitalWrite(ledPin, ledState);
}
return; // don't do the rest
}
if(currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
if (ledState == LOW)
ledState = HIGH;
else
ledState = LOW;
digitalWrite(ledPin, ledState);
}
}
(most of that was copy&paste from the blinkwithoutDelay sketch. No delay() calls at all) Now you have to write the button code. Debouncing without delays might be a bit tricky, so ... let's just defer that for now.
void read_button_and_adjust_time() {
if (read_button_w_debounce()) {
// The button had been pressed. Adjust our time interval. 1000ms, 100ms, off.
if (interval == 1000) {
interval = 100;
} else if (interval == 100) {
interval = 0; // off
} else if (interval == 0) {
interval = 1000;
} else {
// just to be safe, make any unknown interval go to 1000ms
}
}
}
That was straightforward as well, right? The trick is to make each little piece of the program be easy to understand. Reasonably easy, anyway. Now we have reading the button itself. the way the code has been arranged so far, we only want the function to return TRUE when the button changes state, not continually when it is IN a particular state. Now, WHICH state transition you want to sense is up to you. You have UP, debouncing on the way down, DOWN, and debouncing on the way up again. You can act at either the start or the end of the debounce time, as long as you only act once!
#define BUT_UP 0
#define BUT_DBOUNCE 1
#define BUT_DOWN 2
#define BUT_UBOUNCE 3
char button_state = BUT_UP;
boolean read_button_w_debounce() {
static unsigned long changetime; // when the last state change occurred.
boolean pin_state = digitalRead(buttonpin); // (pin state of 0 means pressed)
if (button_state == BUT_UP) {
// Button was not pressed. See if it's pressed now
if (pin_state == LOW) { // Pressed?
button_state = BUT_DBOUNCE; // now we're debouncing the press
changetime = millis();
}
} else if (button_state == BUT_DBOUNCE) {
// Button is being pressed but might be bouncing.
if (pin_state == LOW && (millis() > (changetime + 10))) {
pin_state = BUT_DOWN;
return true; // this is what counts as a button press!
} else if (pin_state == HIGH) { // it looks like the pin is bouncing
changetime = millis(); // so extend our debounce time.
}
} else if (button_state == BUT_DOWN) {
// Button is held down, and has been. Check for release.
if (pin_state == HIGH) { // Released?
button_state = BUT_UBOUNCE;
changetime = millis();
}
} else if (button_state == BUT_UBOUNCE) {
// exercise for the reader :-)
}
return false; //any state or transition that hasn't returned true, returns false
}
This is an example of what's known as "top down" program design; you start with your big problem, and break it apart into smaller pieces until each piece is "easy." The other common technique is "bottom up" design, where you start with pieces that are easy enough to write, and combine them to make bigger programs.